Software Engineering II
01- Introduction to Design Patterns
Strategy Pattern
How to design for flexibility?
Existing Duck application
All ducks quack and swim.
The superclass takes care Duck
of the implementation code quack()
swim() The display() method is
abstract, since all duck
display()
subtypes look different
//other duck-like methods…
Each duck subtype is
responsible for implementing
its own display() method
MallardDuck RedHeadDuck
Other duck types inherit
display() { display() { from the Duck class
// looks like a mallard} // looks like a redhead } ...
2
Testing Mallard, RedHeadDuck classes
3
Changing Requirment
• No sweat!
• Add a method fly() in Duck
• Continue to use inheritance
Duck
quack()
Add a method fly() in Duck swim()
display()
All subclasses fly()
inherit fly() //other duck-like methods…
MallardDuck RedHeadDuck
display() { display() {
// looks like a mallard} // looks like a redhead }
4
Executing
5
Something seriously wrong!
Duck
quack()
swim()
display()
All duck types now
can fly including fly()
RubberDuck
//other duck-like methods…
RubberDuck
MallardDuck ReadHeadDuck quack() {
display() { // overridden to Squeak }
display() {
display() {
// looks like a mallard} // looks like a redhead }
// looks like a rubberduck
}
Strategy Pattern 6
Executing … What?
7
Root cause?
• Applying inheritance to achieve re-use
• Poor solution for maintenance
How do we fix this?
• Using inheritance as before
• Override the fly() method in rubber duck as in quack()
8
Executing
Strategy Pattern 9
Is the problem solved?
• Any new problems?
Wait a minute
• How about new duck types?
• Decoy duck?
• Can’t quack
• Can’t fly
• How do we solve it?
10
Summary
• What have we done so far?
• What problems have we solved?
• What problems have we introduced in solving the problems?
• Is there a better way of doing things?
11
How about Interface?
• Take the fly() method out of Duck superclass
• And make a Flyable() interface
• Only those ducks that fly are required to implement the interface
• Make a Quackable interface too
interface Flyable Interface Quackable class Duck
quack() swim()
fly()
display()
//other duck-like methods…
MallardDuck RedHeadDuck RubberDuck
display() display() {
display()
fly() fly() quack()
quack() quack()
12
But
• You shoot yourself in the foot by duplicating code for every duck type
that can fly and quack!
• And we have a lot of duck types
• We have to be careful about the properties – we cannot just call the
methods blindly
• We have created a maintenance nightmare!
13
Re-thinking:
• Inheritance has not worked well because
• Duck behavior keeps changing
• Not suitable for all subclasses to have those properties
• Interface was at first promising, but
• No code re-use
• Tedious
• Every time a behavior is changed, you must track down and change it in
all the subclasses where it is defined
• Error prone
14
#1 Design Principle
• Identify the aspects of your application that vary and separate them
from what stays the same
• So what are variable in the Duck class?
• Flying behavior
• Quacking behavior
• Pull these duck behaviors out of the Duck class
• Create new classes for these behaviors
15
How do we design the classes to implement the fly and
quack behaviors?
• Goal: to keep things flexible
• Want to assign behaviors to instances of Duck
• Instantiate a new MallardDuck instance
• Initialize it with a specific type of flying
• Be able to change the behavior dynamically
16
#2 Design Principle
• Program to a supertype, not an implementation
• Use a supertype to represent each behavior
• FlyBehavior and QuackBehavior
• Each implementation of a behavior will implement one of
these supertypes
• In the past, we rely on an implementation
• In superclass Duck, or
• A specialized implementation in the subclass
• Now: Duck subclass will use a behavior represented in a supertype.
17
Strategy Pattern 18
classes in code
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements
FlyBehavior { public void fly() {
System.out.println("I'm flying!!");
}
}
public class FlyNoWay implements
FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
} 19
public interface QuackBehavior {
public void quack();
}
20
Specific behaviors by implementing interface QuackBehavior
public class Quack implements QuackBehavior { public void quack() {
System.out.println("Quack");
}
}
public class Squeak implements QuackBehavior { public void quack()
{
System.out.println("Squeak");
}
}
public class MuteQuack implements QuackBehavior { public void quack()
{
System.out.println("<< Silence >>");
}
} 21
22
Integrating the Duck Behavior
1. Add 2 instance variables:
Behavior Duck Instance
variables are variables hold
declared as the FlyBehavior flyBehavior a reference to
behavior QuackBehavior quackBehavior a specific
SUPERTYPE behavior at
performQuack() runtime
Swim()
These general
methods Display()
replace fly() and performFly()
quack()
//OTHER duck-like methods
Strategy Pattern
23
2. Implement performQuack()
public abstract class Duck {
// Declare two reference variables for the behavior interface types
FlyBehavior flyBehavior;
QuackBehavior quackBehavior; // All duck subclasses inherit these
// etc
public Duck(FlyBehavior f, QuackBehavior q) {
}
public Duck() {
}
public void performQuack() {
quackBehavior.quack(); // Delegate to the behavior class
}
24
25
3. How to set the quackBehavior variable & flyBehavior variable
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
// A MallardDuck uses the Quack class to handle its quack,
// so when performQuack is called, the responsibility for the quack
// is delegated to the Quack object and we get a real quack
flyBehavior = new FlyWithWings();
// And it uses flyWithWings as its flyBehavior type
public void display() {
System.out.println("I'm a real Mallard duck");
}
Strategy Pattern 26
Strategy Pattern 27
Testing the Duck code
Type and compile:
• Duck class and the MallardDuck class
• FlyBehavior interface and the two behavior implementation
•
•
classes (FlyWithwings.java and flyNoWay.java)
QuackBehavior interface and 3 behavior implementation classes
Test class (MiniDuckSimulator.java)
Strategy Pattern 28
// 1. Duck class
public abstract class Duck {
// Reference variables for the behavior interface types
FlyBehavior flyBehavior;
QuackBehavior quackBehavior; // All duck subclasses inherit these
public Duck() { }
Is it possible to manage
abstract void display(); all duck’s sub-object
with this super type?
public void performFly() {
flyBehavior.fly(); // Delegate to the behavior class
}
public void performQuack() {
quackBehavior.quack(); // Delegate to the behavior class
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
29
2. FlyBehavior and two behavior implementation classes
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
30
// 3. QuackBehavior interface and 3 behavior implementation classes
public interface QuackBehavior { public void quack();
}
public class Quack implements QuackBehavior { public void quack() {
System.out.println("Quack");
}
}
public class Squeak implements QuackBehavior { public void quack() {
System.out.println("Squeak");
}
}
public class MuteQuack implements QuackBehavior { public void quack() {
System.out.println("<< Silence >>");
}
}
31
4. Type and compile the test class
(MiniDuckSimulator.java)
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
// This calls the MallardDuck's inherited performQuack() method,
// which then delegates to the object's QuackBehavior
// (i.e. calls quack() on the duck's inherited quackBehavior
// reference)
mallard.performFly();
// Then we do the same thing with MallardDuck's inherited
// performFly() method.
}
}
Strategy Pattern 32
At the end: Strategy project
33
Check-in
• We have built dynamic behavior in ducks e.g. a MallardDuck
• The dynamic behavior is instantiated in the duck’s constructor
• How can we change the duck’s behavior after instantiation?
Changing a duck’s behavior after instantiation
• Set the duck’s behavior type through a mutator method on the duck’s subclass
34
How to set behavior dynamically?
1. Add new methods to the Duck class
public void setFlyBehavior (FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
Strategy Pattern 35
2. Make a new Duck type (ModelDuck.java)
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
// Model duck has no way to fly
quackBehavior = new Quack();
}
public void display() {
System.out.println("I'm a model duck");
}
}
Strategy Pattern 36
Enabling ModelDuck to fly
• Use a mutator (setter) method to enable ModelDuck to fly 37
3. Make a new FlyBehavior type (FlyRocketPowered.java)
public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
38
4. Change the test class (MiniDuckSimulator.java), add the
ModelDuck, and make the ModelDuck rocket-enabled
Duck model = new ModelDuck();
model.performFly();
// call to performFly() delegates to the flyBehavior
// object set in ModelDuck's constructor
model.setFlyBehavior(new FlyRocketPowered());
// change the duck's behavior at runtime by
// invoking the model's inherited behavior setter
// method
model.performFly();
39
40
Big Picture on encapsulated behaviors
Reworked class structure
Encapsulated fly behavior
Duck
FlyBehavior flyBehavior
QuackBehavior quackBehavior
Swim()
Display()
performQuack()
performFly()
setFlyBehavior()
setQuackbehavior()
Encapsulated quack behavior
//OTHER duck-like methods
MallardDuck RedHeadDuck RubberDuck
display() display() display()
Strategy Pattern 42
HAS-A can be better than IS-A
• Each duck has a FlyBehavior and a QuackBehavior to which it
delegates flying and quacking.
• Composition at work
• Instead of inheriting behavior, ducks get their behavior by being
composed with the right behavior object.
43
#3 Design Principle
• Favor composition over inheritance
• More flexibility
• Encapsulate a family of algorithms into their own set of classes
• Able to change behavior at runtime
Strategy Pattern 44
Strategy
In a Strategy design pattern, you will:
• Define a family of algorithms
• Encapsulate each one
• Make them interchangeable
45
You should use Strategy when:
• You have code with a lot of algorithms
• You want to use these algorithms at different times
• You have algorithm(s) that use data the client should not know about
46
Strategy Class Diagram
Context Strategy
contextInterface() algorithmInterface()
ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC
algorithmInterface() algorithmInterface() algorithmInterface()
47
Strategy makes this easy!
StrategyX
functionX()
Class
functionX()
functionY()
...
StrategyY
functionY()
...
48
Benefits of Strategy
• Eliminates conditional statements
• Can be more efficient than case statements
• Choice of implementation
• Client can choose among different implementations with different space
and time trade-offs
• Families of related algorithms
• Alternative to subclassing
• This lets you vary the algorithm dynamically, which makes it easier to
change and extend
• You also avoid complex inheritance structures
49
Strategy Pattern
• The strategy Pattern
• Defines a family of algorithms,
• Encapsulates each one,
• Makes them interchangeable.
• Strategy lets the algorithm vary independently from clients that use it
50