The underrated Strategy Design Pattern

Introduction

We are all too familiar with the Singleton and Observable design patterns. Yet, there is the so-called Strategy Design Pattern, which is quite useful but unknown to many developers. This design approach is a very nice example of the favoring composition over inheritance principle, which means: whenever possible aggregate classes instead of simply adding methods to be inherited.

To better explain this design pattern, and the meaning of favoring composition of inheritance, an example will be used, showing first a not-so-good approach, which will then be replaced by a code using the Strategy Design Pattern. Java is the language of choice, but the code is quite simple and can be easily understood even if the reader is not familiar with such language.

The naive implementation

Our problem consists of modeling superheroes and showing their abilities. So, the first class to be presented, is the Superhero abstract class. It has concrete methods, such as puch() and kick(), and it has a abstract one called introduceMyself() which every Superhero implementation must fill in. Below the class implementation is displayed:

public abstract class Superhero {

  public void punch() {
    System.out.println("punch!!");
  }

  public void kick() {
    System.out.println("kick!!");
  }

  public abstract void introduceMyself();

The next step, is to implement the Superhero abstract class. Therefore we have Superman, Wonderwoman, Batman and Robin. As an example, Batman implementation is shown below:

public class Batman extends Superhero {

  @Override
  public void introduceMyself() {
    System.out.println("My name is Batman!!");
  }
}

In order to see the superheroes showing their powers, they are instantiated and and have their abilities being displayed in as follows:

public class AbilitiesDisplay {

  public static void main(String [] args) {
    showSuperheroAbility(new Superman());
    showSuperheroAbility(new Batman());
    showSuperheroAbility(new Robin());
    showSuperheroAbility(new Wonderwoman());
  }

  private static void showSuperheroAbility(Superhero superHero) {
    superHero.introduceMyself();
    superHero.punch();
    superHero.kick();
    System.out.println("________________");
  }
}

The output of the main() method execution is:

My name is Superman!
punch!!
kick!!
________________
My name is Batman!!
punch!!
kick!!
________________
My name is Robin!
punch!!
kick!!
________________
My name is Wonderwoman!
punch!!
kick!!
________________

For a better overview, please check the class diagram below:

naiveimplementation.png

Let's complicate things

So far, so good. Code is being reused, and it seems that this is indeed a good design. But then, it is decided that our superheroes must also show their ability to fly. No problem, let's add one more method to the Superhero. However, note that Superman and Superwoman do fly in the same manner, whereas Batman and Robin do not fly at all. By using this approach we start having some code duplication. The implementation from Superman and Wonderwoman is:

@Override
  public void fly() {
    System.out.println("I am flying!!!");
  }

And the implementation for Batman and Robin is:

@Override
  public void fly() {
    System.out.println("I cannot fly!");
  }

Next, the class diagram provides visual argument for how duplication is occuring regarding the fly() method:

naiveimplementationWithFlyMethod.png

To make things worse, imagine there are 50 Heroes where a few can fly and some others cannot. The amount of duplicated code would be immense. On top of that, there might be other methods to be added with the same issues, like: fireEyeLaser() or becomeInvisible().

A better approach

Instead of implementing every single new ability, what if we code this in separate classes, which the only concern is to produce these new superpowers such as flying or becoming invisible. Then, those classes would be aggregated to the superheroes implementation. Besides favoring composition over inheritance, we would separate what changes from what stays the same, another very nice principle. An example will clarify this idea.

We will have fly and invisibility behaviors defined by the interfaces FlyAbility and Invisibility ability, as shown below:

public interface FlyAbility {
  void fly();
}
public interface InvisibilityAbility {
  void becomeInvisible();
}

FlyAbility will have two implementations: SuperFastFly and NoFlyingPossible, as shown below:

public class SuperFastFly implements FlyAbility {
  @Override
  public void fly() {
    System.out.println("I can fly pretty fast!!!");
  }
}
public class NoFlyingPossible implements FlyAbility {
  @Override
  public void fly() {
    System.out.println("I cannot fly at all!!!l");
  }
}

Invisibility also has two implementations: InvisibilityPossible and InvisibilityPossible, as displayed next:

public class InvisibilityPossible implements InvisibilityAbility {
  @Override
  public void becomeInvisible() {
    System.out.println("I am invisible!");
  }
}
public class InvisibilityImpossible implements InvisibilityAbility {
  @Override
  public void becomeInvisible() {
    System.out.println("I cannot be invisible!!");
  }
}

Now, here comes the gotcha. instead of the Superhero class simply providing abilities methods to be implemented, such as fly() and becomeInvisible(), it will hold references to interfaces of FlyAbility and InvisibilityAbility. These interfaces are responsible for, with their implementations, provide the new abilities, when a Superhero implementation is instantiated. Therefore, the Superhero does not force its children to implement these behaviors, but rather assign an implementation to them via a FlyAbility and InvisibilityAbility implementation using composition. In other words, we are aggregating abilities, not inheriting them. Below is displayed how Superhero is implemented:

public abstract class Superhero {

  protected FlyAbility flyAbility;

  protected InvisibilityAbility invisibilityAbility;

  public void punch() {
    System.out.println("punch!!");
  }

  public void kick() {
    System.out.println("kick!!");
  }

  public void performFly() {
    flyAbility.fly();
  }

  public void becomeInvisible() {
    invisibilityAbility.becomeInvisible();
  }

  public abstract void introduceMyself();
}

As mentioned, references to the FlyAbility and InvisibilityAbility are held, and their implementations will be executed by the methods performFly() and becomeInvisible() respectively. Their implementation will be instantiated in each Superhero child. Superman is an example depicted below:

public class Superman extends Superhero {
  @Override
  public void introduceMyself() {
    System.out.println("My name is Superman!");
  }

  public Superman() {
    flyAbility = new SuperFastFly();
    invisibilityAbility = new InvisibilityImpossible();
  }
}

Observe that Superman has the ability to fly, with the SuperFastFly instantiation of the flyAbility property, but cannot be invisible, since the class instantiates the InvisibilityImpossible class as its invisibilityAbility parameter.

Compare the Superman implementation above, with the naive solution provided before, but displayed here again:

public class Superman extends Superhero {

  @Override
  public void fly() {
    System.out.println("I am flying!!!");
  }

  @Override
  public void introduceMyself() {
    System.out.println("My name is Superman!");
  }
}

In the naive solution above, the fly() method is part of the Superhero specification. Note, that here the method is implemented whereas in the Strategy implementation, the an instance of a Fly algorithm is used - SuperFastFly. This Fly implementation, and many others can be easily used in each Superhero implementation. Observe how elegant the Strategy Design Pattern is.

Below is displayed all other Strategy implementations:

public class Superman extends Superhero {
  @Override
  public void introduceMyself() {
    System.out.println("My name is Superman!");
  }

  public Superman() {
    flyAbility = new SuperFastFly();
    invisibilityAbility = new InvisibilityImpossible();
  }
}
public class Batman extends Superhero {
  @Override
  public void introduceMyself() {
    System.out.println("My name is Batman!!");
  }

  public Batman() {
    flyAbility = new NoFlyingPossible();
    invisibilityAbility = new InvisibilityImpossible();
  }
}
public class Robin extends Superhero {
  @Override
  public void introduceMyself() {
    System.out.println("My name is Robin!");
  }

  public Robin() {
    flyAbility = new NoFlyingPossible();
    invisibilityAbility = new InvisibilityImpossible();
  }
}

Note, how all heroes use the new abilities in a similar fashion: by instantiating each ability in the Superhero related parameter. Continuing, the class below shows our heroes abilities being showcased:

public class AbilitiesDisplay {

  public static void main(String [] args) {
    showSuperheroAbility(new Superman());
    showSuperheroAbility(new Batman());
    showSuperheroAbility(new Robin());
    showSuperheroAbility(new Wonderwoman());
  }

  private static void showSuperheroAbility(Superhero superHero) {
    superHero.introduceMyself();
    superHero.punch();
    superHero.kick();
    superHero.performFly();
    superHero.becomeInvisible();
    System.out.println("________________");
  }
}

Note that nothing was changed in AbilitiesDisplay except for the new abilities.

Below the result of the above main() method execution, which shows the expected results:

My name is Superman!
punch!!
kick!!
I can fly pretty fast!!!
I cannot be invisible!!
________________
My name is Batman!!
punch!!
kick!!
I cannot fly at all!!!l
I cannot be invisible!!
________________
My name is Robin!
punch!!
kick!!
I cannot fly at all!!!l
I cannot be invisible!!
________________
My name is Wonderwoman!
punch!!
kick!!
I can fly pretty fast!!!
I am invisible!

And finally the class diagram of the presented solution:

strategyimplementation.png

Strategy Desgin Pattern definitions

Here are a few definitions of this design patterns:

From Wikipedia:

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

From Head First Design Patterns

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.

A few last words

Note how using the Strategy Design Pattern, code reuse is beautifully applied. The abilities in our example, can be used easily in each instantiation of the Superhero class. Moreover, adding new behavior is easily achieved by providing another ability reference. Furthermore, another very nice side effect is that things that change, such as the abilities, are separated from the main code - superhero class and its children - which serves well the principle: separate what changes from what stays the same.

You can find the complete source code here.

No Comments Yet