Strategy Pattern | Head First Design Patterns

See more articles in OS/Patterns category

I summarized the patterns introduced in the book Head First Design Patterns.
In this book, 12 design patterns are introduced.

1. Strategy Pattern
2. Observer Pattern
3. Decorator Pattern
4. Factory Pattern
5.. Singleton Pattern
6. Command Pattern
7. Adapter and Facade Patterns
8. Template Method Pattern
9. Iterator and Composite Patterns
10. State Pattern
11. Proxy Pattern
12. Compound Patterns

What is Strategy Pattern? (Definition)

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.



Explanation of the use of Strategy Pattern


Initial Design
There is a Objected-Oriented programmer whose name is Joe works for a company that makes a highly successful duck pond simulation games, SimUDuck. The initial design was like below.



But, now we need the ducks to Fly
The executives decided to add Fly behavior into Duck characters.


So, What did Joe do?
Joe simply added the fly() method in the Duck class so that all subclasses of Duck class can inherit it.



But, something went horribly wrong~
What happened?
Joe failed to notice that not all subclassof Duck should fly. When Joe added new behavior to the Duck superclass, he was also adding that was not appropriate for some Duck subclasses.

A localized update to the code caused a non-local side effect(flying rubber ducks)!

What he thought was a great use of inheritance for the purpose of reuse hasn't turned out so well when it comes to maintenance.



Joe thinks about inheritance..
Joe thinks that he could always just override the fly() method in rubber duck, the way he is with quack() method..



But, then what happens when Joe add wooden decoy ducks to the program? They are not supposed to fly or quack.




How about an interface?
Joe realized that inheritance probably was not the answer because he just got a memo that says that the executives now want to update the product every six months (in ways they haven't decided on). Joe knows that the spec will keep changing and he will be forced to look at and possibly override fly() and quack for every new Duck subclass that's ever added to the program.. forever

So, he needs a cleaner way to have only some(but not all) of the duck types fly or quack.


The Flyable and Quackable interface sounded promising at first - only ducks that really do fly will be Flyable, etc - except Java interfaces have no implementation code, so no code reuse. And that means that whenever you need to modify a behavior, you are forced to track down and change it in all the different subclasses where that behavior is defined, probably introducing new bugs along the way!



Zeroing in on the problem...
Design Principle
Identify the aspects of your application that vary and separate them from what stays the same.

Another way to think about this principle: take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don't.

Separating what changes from what stays the same
We know that fly() and quack() are the parts of the Duck class that vary across ducks.

To separate these behaviors from the Duck class, we will pull both methods out of the Duck class and create a new set of classes to represent each behavior.


Designing the Duck Behaviors
Design Principle
Program to an interface, not an implementation.

The Duck subclasses will use a behavior represented by an interface(FlyBehavior and QuackBehavior), so that the actual implementation of the behavior(in other word, the specific concrete behavior coded in the class that implements the FlyBehavior or QuackBehavior) won't be locked into the Duck subclass.


Q. Why do we have to use interface for FlyBehavior instead of
    abstracts superclass?


A. "Program to an interface" really means "Program to a supertype."


Programming to an implementation example:
Dog d = new Dog();
d.bark();

Programming to an interface / supertype example:
Animal animal = new Dog();
animal.makeSound();

Assign the concrete implementation object at a runtime example:
a = getAnimal();
a.makeSound();



Implementing the Duck Behaviors

With this design, other types of objects can reuse our fly and quack behaviors because these behaviors are no longer hidden away in our Duck classes!

And we can add new behaviors without modifying any of our existing behavior classes or toucking any of the Duck classes that use flying behaviors.

Q. It feels a little weird to have a class that's just a behavior. Aren't classes supposed to represent things? Aren't class supposed do have both state AND behavior?

A. In an OO system. yes classes represent thing that generally have both state(instance variable) and methods. And in this case, the thing happens to be a behavior, But even a behavior can still have state and methods; a flying behavior might have instance variables representing the attributes for the flying(wing beats per minute, max altitude and speed, etc.) behavior.



Integrating the Duck Behavior
The key is that a Duck will now delegate its flying and quacking behavior, instead of using quacking and flying methods defined in the Duck class(or subclasses).



The Big Pictures on encapsulated behaviors
Instead of thinking the duck behaviors as a set of behaviors, we'll start thinking of them as a family of algorithms.




CODE
public class MiniDucksSimulator {

  public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performQuack();
    mallard.performFly();
    
    Duck model = new ModelDuck();
    model.performFly();
    // To change a duck's behavior at runtime, just call the duck's setter
    // method for that behavior.

    model.setFlyBehavior(new FlyRocketPowered());
    model.performFly(); 

  }
}
// end class
Result---------------------------------------------
   Quack
   I'm flying!!
   I can't fly
   I'm flying with a rocket
---------------------------------------------------
public abstract class Duck{

  // instance variables
  FlyBehavior flyBehavior;
  QuackBehavior quackbehavior;  

  // constructor
  public Duck(){ }

  public abstract void display();

  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!");
  }

  // added to set behavior dynamically
  public void setFlyBehavior(FlyBehavior fb) {
    flyBehavior = fb; 
  }

  // added to set behavior dynamically
  public void setQuackBehavior(QuackBehavior qb) {
    quackBehavior = qb;
  }

}
// end class
public class MallardDuck extends Duck {

  public MallardDuck() {
    quackBehavior = new Quack();
    flyBehavior = new FlyWithWings();
  }

  public void display() {
    System.out.println("I'm a real Mallard duck");
  }
}// end class
 public class ModelDuck extends Duck {

  public ModelDuck() {
    flyBehavior = new FlyNoWay();
    quackBehavior = new Quack();
  }
  
  public void display() {
    System.out.println("I'm a model duck");
  }

 }// end class
public interface FlyBehavior {
  public void fly();
}// end class
public FlyWithWings implements FlyBehavior {
  public void fly(){
    System.out.println("I'm flying!!"):
  }
}// end class
public FlyRocketPowered implements FlyBehavior {
  public void() {
    System.out.println("I'm flying with a rocket!");
  }
}// end class
public interface QuackBehavior {
  public void quack();
}// end class
public class Quack implements QuackBehavior {
  public void quack() {
    System.out.println("Quack");
  }
}// end class
public class MuteQuack implements QuackBehavior {
  public void quack() {
    System.out.println("<< Silence>>");
  }
}// end class
public class Squeak implements QuackBehavior {
  public void quack() {
    System.out.println("Squeak");
  }
}// end class


HAS-A can be better than IS-A
Design Principle
Favor composition over inheritance.

Each duck has a FlyBehavior and a QuackBehavior to which it delegates flying and quacking. When you put two classes together like this you are using composition.

1) Using composition gives you a lot more flexibility.
2) Using composition lets you encapsulate a family of algorithms into their own
    set of classes.
3) Using composition lets you change behavior at runtime as long as the object
    you're composing with implements the correct behavior interface.



One of the secrets to creating maintainable OO systems is thinking about how they might change in the future and these principle address those issues.

'OS > Patterns' 카테고리의 다른 글

Strategy Pattern | Head First Design Patterns  (0) 2010.03.09