Mícéal Gallagher in Design-patterns 3 minutes

Design Patterns - The Strategy Pattern

We have an application that simulates birds called Bird-O-Sim. When it was built, and only had to support hawks and eagles, the class diagram looked a little like the one below.

Base Bird-O-Sim

Simple. The superclass Bird takes care of the fly() method and the sub-types (Eagle and Hawk) are responsible for their own display() implementation.

Bird-O-Sim needs to run on Linux. That means we need to support penguins. Any developer worth their salt knows; linux is an amazing operating system penguins can’t fly.

We could make the superclass method fly() abstract, then add an empty fly() method on a new Penguin sub-type. Like so…

Base Bird-O-Sim

Not bad, but not great either. Some of the sub-types take care of fly(), while others don’t. This isn’t unreasonable, but it lacks consistency.

Another potential solution is adding an if-statement in the superclass to fly or not to fly.

public class Bird {
  ...
  public void fly() {
    if ( instanceOf(Penguin.class) ) {
      // Don't fly
    } else {
      // Fly!
    }
  }	
  ...
}

That would work. Although, if we decide to support Ostriches this code will start to become unsightly.

if ( instanceOf(Penguin.class) || instanceOf(Ostrich.class) ) {
  // Don't fly
} else {
  // Fly!
}

It could be said that flying is a behavior of a bird. In the case of penguins and puffins, it could be said that swimming is also a behaviour of a bird. These behaviours vary widely from bird to bird and at times are not present at all. When structuring code it’s important to isolate what changes from what stays the same.

Base Bird-O-Sim

Let’s move all bird behaviours to separate classes- clasess that implement a particular interface.

Base Bird-O-Sim

With this design other sub-types can reuse our behaviors as they are no longer hidden in Bird classes. We can also add new behaviors without having to modify existing behaviors.

Now we’ll extend the Bird superclass to support behaviors

Base Bird-O-Sim

Here’s the big picture

Base Bird-O-Sim

Now we can compose our bird objects with our reuseable behaviors. Notice how puffin can use flyWithWings and swimWithWings.

// Prepare the behaviors
FlyBehavior  flyWithWings  = new FlyWithWings();
FlyBehavior  flyNever      = new FlyNever();
SwimBehavior swimWithWings = new SwimWithWings();
SwimBehavior swimNever     = new SwimNever();
// Prepare the brids
Bird hawk    = new Bird(flyWithWings, swimNever);
Bird eagle   = new Bird(flyWithWings, swimNever);
Bird penguin = new Bird(flyNever, swimWithWings);
Bird puffin  = new Bird(flyWithWings, swimWithWings);

Final thoughts

The strategy pattern is filled with a lot of good OO practices:

  • Isolate what changes from what stays the same. We did this by defining Swim and Fly behaviors
  • Program to an interface not an implementation. All our behaviors conform to well defined interfaces
  • Favor composition over inheritance. Composition allows us to group a family of algorithms such as our behaviors.
  • Classes should be open for extension and closed for modification. All behaviors are in closed classes yet our superclass Bird can be easily extended with new behaviors.