The Strategy Pattern in Java

Introduction to the Strategy Pattern

Design pattern in a simple meaning, is a way to design reusable object-oriented code. We can think of ways to design our classes, interfaces, enums and their members so that the code is reusable but flexible.

There are 23 most important design patterns, pioneered by the book Design Patterns: Elements of Reusable Object-Oriented Software, written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides or Gang of Four for short. We will cover these design pattern one by one.

The strategy pattern defines a family of algorithms, encapsulates each one and makes them interchangeable.

Implementation of Strategy Pattern in Java

Now, say we have an abstract class Animal as base class for a lot of animals like, Dog, Bird classes. We know that each animal will have the ability to fly or not, the Bird can fly but the Dog cannot. So at first we create the Flyable interface in src folder:

public interface Flyable {
  void fly();
}

When the Animal implements this method, it can output “I can fly” or “I can not fly”. Very simple, now the question is will we let the Animal class implement the Flyable interface? The answer is not, because the Animal will be tightly coupled with the Flyable interface. Instead we should compose the Flyable into the Animal class, like this:

public abstract class Animal{
  private final String name;
  private Flyable flyable;
  public Animal(String name){
    this.name = name;
  }
  private String getName() {
    return this.name;
  }
  //set the ability to fly dynamically
  public void setFlyable(Flyable flyable){
    this.flyable = flyable;
  }
  //call the fly() method of the flyable
  private void fly(){
    if (flyable != null) flyable.fly();
  }
  public void doAllThings(){
    System.out.print("I am a " + getName() + ", ");
    fly();
  }
}

Until now we can see that in design patterns, composition should be considered before inheritance. Next, will we implement the fly() method in each Dog and Bird class? Now, the Strategy pattern shines. This pattern advises us to encapsulate the algorithms of Flyable, with each algorithm will be encapsulated in a separate class. The client of Flyable (which are the Animal and its subclasses) can choose the right class that suits the need. So we create two classes, the FlyNormal class:

public class FlyNormal implements Flyable {
  public void fly() {
    System.out.println("I can fly");
  }
}

And the FlyNone class:

public class FlyNone implements Flyable {
  public void fly() {
    System.out.println("I can not fly");
  }
}

Now, in the Bird class we will choose the FlyNormal class:

public class Bird extends Animal {
  public Bird() {
    super("Bird");
    setFlyable(new FlyNormal());
  }
}

Note that we can call the setFlyable() method here, or we can call it dynamically at runtime. And next in the Dog class we will choose the FlyNone class:

public class Dog extends Animal {
  public Dog() {
    super("Dog");
    setFlyable(new FlyNone());
  }
}

Now, in Test.java:

public class Test {
  public static void main(String[] args){
    Dog dog = new Dog();
    dog.doAllThings();
    Bird bird = new Bird();
    bird.doAllThings();
  }
}

Run the code and we have the output as what we expected:

I am a Dog, I can not fly
I am a Bird, I can fly

The Strategy helps us to decouple the Flyable and the Animal class. More than that, it can help us when we need to add more algorithms to the Flyable.

Say, we create a new Animal class, that is a RobotBird with the Flyable action to be “I can fly like a rocket”. If we didn’t use the Strategy, we would have to come back to Animal class and change a lot of code. But now we can simply add a FlyRocket class like this:

public class FlyRocket implements Flyable{
  public void fly() {
    System.out.print("I can fly like a rocket");
  }
}

And the RobotBird class itself:

public class RobotBird extends Animal{
  public RobotBird(String name) {
    super("Robot Bird");
    setFlyable(new FlyRocket());
  }
}

Now in Test.java, we add the new code:

public class Test {
  public static void main(String[] args){
    Dog dog = new Dog();
    dog.doAllThings();
    Bird bird = new Bird();
    bird.doAllThings();
    //add the RobotBird
    RobotBird robotBird = new RobotBird();
    robotBird.doAllThings();
  }
}

The output will be:

I am a Dog, I can not fly
I am a Bird, I can fly
I am a Robot Bird, I can fly like a rocket

The Strategy demonstrates a very important principle of software development. Our class should be closed to modification but open to extension. Each class of the Flyable family is encapsulated so we can add more algorithms to the Flyable family without breaking the current code.

Leave a Reply