strategy and template pattern
DESCRIPTION
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."TRANSCRIPT
Agenda Strategy Pattern
SimUDuck Application (Head First) Setting Behavior Dynamically Favor Composition over Inheritance Lab Refactoring w/Strategy Dependency Injection
Template Pattern Hooks Hollywood Principle
204/10/23
SimUDuck Application (Head First Design Patterns)
pg 2 in book.
Duck can Quack Swim Display – handles visual display
It is assumed that all Ducks have the same behavior for Quack and Swim.
304/10/23
SimUDuck Application
pg 3
Now fly() behavior is added to the superclass. This is a new requirement! Problem since RubberDucks don’t fly!
pg 4-5. How can this problem be solved? We could override fly() in RubberDuck to do nothing. Also note that RubberDucks do not quack..they Squeak.
What was good for reuse is bad for maintenance. Note: a change to the superclass unintentionally can affect all
subclasses. You would have to inspect each subclass.404/10/23
Using Interface
pg 6 – we learn that the superclass flying and quacking behavior will constantly change (due to changing customer requirements) What would need to happen every time the behavior changed?
Joe creates a Flyable and Quackable interface and applies it to the classes which has those behaviors. He pulls Fly and Quack out of the superclass. How will each Duck sublcass define those behaviors? Could there be possible duplication of code? Ie, MallardDuck’s
implementation of fly() versus RedheadDuck’s implementation. What if there are different variations of flying?
504/10/23
The problem… Inheritance didn’t work
Not all subclasses need to Fly or Quack. Making a change to superclass will cause a maintenance
headache.
Flyable/Quackable interface didn’t work No code re-use among the different Fly/Quack behaviors. If Fly changed, you would need to track down all subclasses that
use the Flyable interface.
Design Principle: “Identify the aspects of your application that vary and separate them from what stays the same.” You can alter or extend parts that vary without affecting the parts
that are static. 604/10/23
Back to SimUDuck
Which part varies? What part does not?
How can we pull out the behavior that varies from the Duck class?
Also, how can we extend this behavior in the future without having to re-write existing code?
704/10/23
Closer Look at Variable Behavior pg 13: Create two sets of classes (one for Flying and
one for Quacking). These two sets of classes represents the behavior that varies.
We’d like the behaviors to be flexible, for example: Create a MallardDuck that can fly and quack. But then, by a
simple code change, change the MallardDuck so that it cannot fly and that when it quacks, it’s actually the song “Gimme back that Filet-O-Fish.”
We don’t want the Duck class to be tied to a specific behavior of Fly or Quack. What if the Duck class was tied to an abstraction of the behavior??
804/10/23
Design Principle #2
“Program to an interface, not an implementation.” (I like “program to an abstraction, not an implementation.”)
Define the interfaces FlyBehavior and QuackBehavior. The Duck class will be tied to these interfaces, but not a specific implementation.
Each Duck subclass will use a specific implementation of this behavior.
904/10/23
Breaking it down…
pg 13. FlyBehavior and QuackBehavior The FlyBehavior and QuackBehavior represents an abstraction
of the flying and quacking. FlyWithWings, FlyNoWay, Quack, etc are implementations of
these behaviors.
pg 15. The Duck class contains instance variables of FlyBehavior and QuackBehavior. Here is an example of programming to an abstraction. The Duck
class (an abstraction itself) is composed of the abstraction of two behaviors.
Pg 15. performQuack function() The operation of Quacking is delegated! The Duck class does
not do it itself. 1004/10/23
Breaking it down… (cont)
Pg 16. In the constructor of MallardDuck, the quack and fly behavior is set up. Note: On pg 17, the book admits that this constructor isn’t an
ideal solution. This does violate the principle of “programming to an implementation” since we are tying the MallardDuck to a specific behavior.
However, this can be changed dynamically.
pg 18 Testing the Duck Code
1104/10/23
Setting Behavior Dynamically
pg 20.
1. Add two new methods to Duck class
2. ModelDuck is created with some default behavior.
3. Create a new fly behavior
4. Note that ModelDuck is created..then the behavior is changed.
1204/10/23
To Think About
If we had used inheritance for Fly(), could you change the Fly behavior at runtime?
Look at the FlyBehavior classes…what if there is common functionality amongst all of the FlyBehavior classes? Instead of an interface, use an abstract class for FlyBehavior. Think of this as a “family of algorithms”
1304/10/23
Favor Composition over Inheritance
Pg 23
With Composition, we were able to dynamically change behavior at runtime.
With Inheritance, behavior was decided a compile time. You could not change at runtime when you use inheritance.
1404/10/23
The Strategy Pattern
GoF Intent: “Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”
1504/10/23
Labpublic class Logger {
public void LogMessage(string message, int level) {
PrintSteam[] files = OpenFiles();
WriteLogMessage(files, message, int level);
CloseFiles(intfiles);
}
private void WriteLogMessage(PrintStream[] files,
string message, int level) {
for (int i=0; i < files.Length; i++) {
if (level == 1) {
//Write error message to log file.
else if (level == 2)
//Write error message to log file + Log the
//UserName and IP address.
else if (level == 3)
//Write error message to log file. + Send email to
//administrator
}
}16
Lab (cont)Question 1: Which part of the code varies?
Question 2: What part of the code does not vary?
Question 3: Using inheritance, describe how you could isolate the parts that vary.
Question 3a: (Continuing from Question 3 with inheritance) Let’s say we learn that the function OpenFiles has different behavior as well. This behavior is completely unrelated to the logging level. We start by creating if/else statements inside this function. We get in trouble from our manager for doing this since we know that there will be future change. Can we use inheritance to fix this problem?
1704/10/23
Lab (cont)
Question 4: Using composition, design out how you would isolate the parts that vary.
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.
Question 4a: How can we change the logging level during runtime?
1804/10/23
Lab (cont)
Question 4b: We get some flexibility by changing the behavior during runtime, but this also means that the person who is coding the Context object (i.e., Logger) must be aware of all possible behaviors. For example, to call setLogWriter here, you would need to be aware of LogWriter1 and LogWriter2.
Logger logger = new Logger()
Logger.setLogWriter(new LogWriter1());
Logger.setLogWriter(new LogWriter2());
Is there an alternative to sending an instance of LogWriter1 or LogWriter2, and yet achieve the same results?
1904/10/23
Lab (cont)
Question 4c: Let’s say the Logger class contains the properties LoggedInUser and LastTimeLogged. We know that most of the log writers (LogWriter1, LogWriter2, etc) need access to these properties. What can we do?
Bonus Question: For a Logger implementation (such as LogWriter1), how many instances do we really need?
2004/10/23
Lab Answers
Question 1 - In WriteLogMessage function, the if/else statement with the three different options.
Question 2: The three lines of code in function LogMessage The for loop declaration in function WriteLogMessage
2104/10/23
Lab Answers
Question 3
public abstract class Logger {
public void LogMessage(string message) {…}
private void WriteLogMessage(PrintStream[] files, string message) {
for (int i=0; i < files.Length; i++) {
WriteLogMessageImpl(PrintStream file, message);
}
}
protected abstract WriteLogMessageImpl(PrintStream file, string msg);
}
2204/10/23
Question 3a:
Lab Answers
Question 4
public interface ILogWriter {
void Write(PrintStream file, string message);
}
public class Logger {
public ILogWriter logWriter;
private void WriteLogMessage(PrintStream[] files, string message) {
for (int i=0; i < files.Length; i++) {
logWriter.Write(file, message);
}
}
}
2304/10/23
Question 4a
public void setLogWriter(ILogWriter writer) {
this.logWriter = writer;
}
2404/10/23
Question 4bpublic enum LogWriterEnum {
Simple,
Medium,
Hard
}
public void setLogWriter(LogWriterEnum logEnum) {
if (logEnum == Simple)
this.logWriter = LogWriter1();
else if (logEnum == Medium)
this.logWriter = LogWriter2();
…
}
Alternative Design:
http://www.codeproject.com/KB/architecture/FactoryStrategyDesignPatt.aspx
2504/10/23
So what if we created an enum…
Question 4c
Option 1
public interface ILogWriter {
void Write(PrintStream file, string message,
string LoggedInUser, string LastTimeLogged);
}
We can send the information in the method call itself. The disadvantage is that we may be sending more information than needed.
2604/10/23
Question 4c
Option 2 – We can pass a reference of Context (Logger) itself
public interface ILogWriter {
void Write(PrintStream file, string message, Logger log);
}
Advantage - The strategy can ask the Logger class for information that it needs. (and it may not need anything!)
Disadvantage – The strategy becomes more tightly coupled with the Context object.
2704/10/23
Bonus Question
We can use Singletons!
Instead of this code:
Logger.setLogWriter(new LogWriter1());
Logger.setLogWriter(new LogWriter2());
We can use this code:
Logger logger = new Logger()
Logger.setLogWriter(LogWriter1.TheInstance);
Logger.setLogWriter(LogWriter2.TheInstance);
2804/10/23
Advantage: Unit Testing
What if a new behavior is needed? Ie, new FlyBehavior, new QuackBehavior, new LogWriter, etc
The new class can be created and unit tested on its own (in complete isolation of the infrastructure that uses it).
Another developer who does not know the “Big Picture” can easily be given direction to create the new behavior and unit test it.
2904/10/23
Refactoring w/Strategy
public class SomeClass() {
public void Function1() {
//section of code that does not vary
//The next line of code will vary
int i = (SOME_CONST*50)+100;
//section of code that does not vary
}
}
3004/10/23
First question you need to ask yourself: use inheritance or composition?
Refactoring (cont)
public interface IComputeStrategy {
int Compute();
}
public class DefaultComputeStrategy implements IComputeStrategy {
public int Compute() {
return (SOME_CONST*50)+100;
}
}
3104/10/23
Refactoring (cont)
public class SomeClass {
private IComputeStrategy strategy;
public SomeClass() {
this.strategy = new DefaultComputeStrategy();
}
public void Function1() {
//section of code that does not vary
int i = strategy.Compute();
//section of code that does not vary
}
}
3204/10/23
Dependency InjectionTechnique for providing an external dependency to a software
component.
In the Refactoring example, SomeClass is dependent upon a concrete implementation:
public SomeClass() {
this.strategy = new DefaultComputeStrategy();
}
But what if we did this… public SomeClass(IComputerStrategy strategy) { this.strategy = strategy;
}
3304/10/23
Injection References
http://martinfowler.com/articles/injection.html http://msdn.microsoft.com/en-us/magazine/
cc163739.aspx
3404/10/23
Summary
Pattern Name – Strategy Problem – Different algorithms will be appropriate at
different times. Need a way for an object to use different algorithms at runtime.
Solution Define a family of classes that use the same interface Provide a set method so that the behavior can be set (and
changed) at runtime.
Consequences Hierarchy of strategy classes Alternative to subclassing Eliminates conditional statements Clients must be aware of strategies Communication between Context and Strategy
3504/10/23
Template
pg 276 contains the requirement on brewing coffee and tea.
Look at code on pg 277 (Coffee) and pg 278 (Tea)
How many different steps are there?
What is similar?
What is different?
3604/10/23
Template
Pg 280. Coffee’s implementation of prepareRecipe() would have
to call the following: boilWater brewCoffeeGrinds pourInCup addSugarAndMilk
Tea’s implementation of prepareRecipe() would have to call the following: boilWater steepTeaBug pourInCup addLemon
3704/10/23
Template
Pg 282
The second step of Coffee (brewCoffeeGrinds) and the second step of Tea (steepTeaBag) can be abstracted to a function called “brew”.
The last step can be abstracted to a function “addCondiments”
3804/10/23
Template
Pg 283
The class CaffeineBeverage contains the function prepareRecipe that contains the four steps. Note that it is marked as final. The steps brew and addCondiments have been marked as
abstract.
The sub-classes will specify the exact implementation of brew and addCondiments.
3904/10/23
Template Method Pattern
GoF Intent: “The Template Method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of the algorithm without changing the algorithm’s structure.”
4004/10/23
Hooks
“A hook is a method that is declared in the abstract class, but only given an empty or default implementation.”
Hook may be optional! Empty Implementation
See pg 292 for an example.
The hook could be completely empty as well!
4104/10/23
Hollywood Principle
“Don’t call us, we’ll call you.”
Promotes working with abstractions as much as possible.
Good for creating frameworks.
4204/10/23