This is Class 2 on a 6 week course I taught on Software Design Patterns.
This course discusses Strategy and Template pattern.
Class based on "Head First Design Patterns."
A duck pond simulation game. Assume that there is a screen with a pond. There are buttons for Quack, Swim, Fly, etc. You can select a Duck and say “do this”. Or you can press a button and say “All Ducks Quack”. “All Ducks Swim”. “All Ducks Fly”, etc. Also, let’s say we have a Vision Statement from the Tech Lead: be able to deploy the application once…then apply patches when needed. This patches should not affect code all ready deployed..simply extend when we already have. This will simplify re-testing everything! 13 November 2008
Pg 2. Fly is added. This is a problem since not all Ducks fly. So if you add a new Duck to the pond, it will automatically fly when the user presses that “All Duck Fly” button. Pg 4 With Inheritance, we see that not all derived classes necessarily use the behavior of the base class (as requirements change). We could of course use more inheritance to abstract out different behaviors…but imagine what happens every time a requirements change happens? You may have to completely change your inheritance hierarchy and re-shift things around. It may be easier to just put stuff in the base class and then inherit, and then overwrite the behavior with null implementation. Inheritance implies a compile time decision. Note when happens with a DecoyDuck is added. You have to do a lot of maintenance here. 13 November 2008
Having Fly and Quack in the superclass didn’t make sense here. Now we learn it will be changing a lot in the future. A Duck may Fly one day and then the customer says it should not fly anymore. Also, the user at runtime wants to change the behavior. 13 November 2008
This is a very important principle. If you have an IF/ELSE block and you know that it will never never change, then it is probably good as-is. If you have an IF/ELSE and you know that there is a lot of potential change, then it is a good idea to extend. 13 November 2008
Part that varies: Flying and Quacking Part that is the same: Displaying, Swimming Let’s pull out Fly and Quack into their own separate objects. 13 November 2008
13 November 2008
Note that FlyBehavior and QuackBehavior are Interfaces. They do not need to be. What if there was a lot of common behavior behind Flying and Quacking? You may want an abstract class here instead. The basic concept here is that there an abstraction here. This is a very important concept. Think about the Singleton example I gave last week. The Singleton was tied to a specific instance of Oracle database. If you were trying to create a Common Component for your Enterprise, that Singleton would be a bad example example since it was tied to Oracle. You probably would want an object (and perhaps not a Singleton) that is designed towards an abstraction of a database. In general, this concept is key for designing frameworks or other base classes. If you are creating a base class with a lot of sub-classes, look closely. Does the base class rely on concrete implementations or abstractions?? 13 November 2008
Pg 18 Step 1 – Abstract Duck. Note that is composed of Fly and Quack behavior. performFly and performQuack both delegate to these behaviors. Look at swim. This implies that all ducks swim. I would mark this method as final so that no one can override, else we have the same inheritance problem that we saw earlier. Look at display. It is abstract. Could we have used a Display behavior here instead?? Just like Fly and Quack??? Well, we knew that Fly and Quack will be changing a lot, so that is why we did not want to declare that behavior in the base class. Display, however, does not change. A MallardDuck will have a unique display (ie,. The image of the MallardDuck). So inheritance here is used and that is OK. Step 2 and 3 – Defines different behaviors. Note that these behaviors are a separate set of classes. There is no relation to Duck. Step 4 – what happens when mallard.performQuack() is called?? Duck.performQuack() is called It delegates to FlyBehavior.Fly(). In the case of Mallard, that is FlyWithWings.fly(). 13 November 2008
Step 1 – these can be called at any time to change behavior! Even though we had some behavior set up in the constructor. Step 2 – we create a new type of Duck Step 3 – we create a new type of behavior Step 4 – we create a new ModelDuck. We say fly. Then we dynamically change the behavior…and then say fly again. 13 November 2008
Big difference between Inheritance and Composition: With Inheritance, you are statically defining behavior at the time of coding. With Composition, you have the option to dynamically change behavior. The requirements can help dictate whether to use inheritance or composition. 13 November 2008
This does not mean that Inheritance is bad! 13 November 2008
Go through the lab together. 13 November 2008
13 November 2008
You would have three classes: LogLevel1 LogLevel2 LogLevel3 Each class describes the log implementation. What is the requirement came that the log level must be changed dynamically by the user at runtime?? We would need to manage the correct LogLevel class I guess! Question 3a: Since we have already used inheritance, we really can’t use it again! We would have to use composition here…even if we don’t care about the runtime requirement. Each LogLevelXX class can use all possible behaviors of OpenFiles. If the log level does not need to change during runtime, then this implementation is fine. But if the Log Level also needs to vary during runtime, then this is not a good implementation. We would want to use composition to vary both OpenFiles and WriteLogMessage. 13 November 2008
Hint #1 : Create an interface that represents the variable behavior Hint #2 : Compose Logger with this variable behavior. Hint #3: Have Logger delegate to this behavior. 13 November 2008
13 November 2008
What is wrong with this? If you add another logwriter, you need to make two changes: Add a new entry into LogWriterEnum You need to update the setLogWriter every time you change LogWriterEnum. We could have the function talk to another object outside of Logger completely. It could ask: Give me the LogWritter object after I give you this enum. By doing this, it keeps any changes that you need to update within Logger itself. 13 November 2008
Discuss SQL parser. I know there is change coming in different areas, for example: SQL Operator (AND, OR, etc) or how to parse a leaf node (EXISTS(XXX) or Column or Inner Query). I have coded this out into a separate class for easy extension. This also has a nice side-benefit, I could easily give the extended behavior to another developer and the developer doesn’t need to fully understand the infrastructure that code lives in. They could just focus on the new behavior. 13 November 2008
Not Shown: A Setter 13 November 2008
Functionally, we have not changed anything. We can use unit tests to verify. But what is wrong with the new DefaultBehavior() line? It violates the rule of programming to an interface. Alternative: create a getter(). If the strategy is null, then return a default implementation. 13 November 2008
The new() operator for creating a new strategy is now nowhere to be seen in this class. This implies that the Client must provide the implementation. Why is this important? Imagine again the Database Object that is written to use a generic Db objects. You put this in a common component. Then different implementations can call this code with the concrete object that is needed. Also useful for unit testing for injecting a mock object. Briefly discuss the problems with Singletons…you cannot access the private constructor very easily. This idea is the basis for Spring and similar frameworks. This example is simple. 13 November 2008
4 steps. boilWater and pourInCup are exactly the same! The 2 nd and 4 th steps are not quite the same…but very similar. 13 November 2008
The code does have boilWater and pourInCup in common..so that is good. That code is encapsulated in one place. What happens if a fifth step is added? You would have to change both Coffee and Tea. Is this a good design? Note that the order that these methods are called are not enforced. You could easily change the order of the four functions of Tea! What happens if a new developer comes? Is it apparent that these are the correct order of steps? 13 November 2008