CodeGym /Java Blog /Design Patterns in Java /Strategy Design Pattern
Author
John Selawsky
Senior Java Developer and Tutor at LearningTree

Strategy Design Pattern

Published in the Design Patterns in Java group
Hi! In today's lesson, we'll talk about Strategy pattern. In previous lessons, we've already briefly become acquainted with the concept of inheritance. In case you forgot, I'll remind you that this term refers to a standard solution to a common programming task. At CodeGym, we often say that you can google the answer to almost any question. This is because your task, whatever it is, has probably already been successfully solved by someone else. Patterns are tried-and-true solutions to the most common tasks, or methods for solving problematic situations. These are like "wheels" that you don't need re-invent on your own, but you do need to know how and when to use them :) Another purpose for patterns is to promote uniform architecture. Reading someone else's code is no easy task! Everyone writes different code, because the same task can be solved in many ways. But the use of patterns helps different programmers understand the programming logic without delving into each line of code (even when seeing it for the first time!) Today we look at one of the most common design patterns called "Strategy". Design pattern: Strategy - 2Imagine that we're writing a program that will actively work with Conveyance objects. It doesn't really matter what exactly our program does. We've created a class hierarchy with one Conveyance parent class and three child classes: Sedan, Truck and F1Car.

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
All three child classes inherit two standard methods from the parent: go() and stop(). Our program is very simple: our cars can only move forward and apply the brakes. Continuing our work, we decided to give the cars a new method: fill() (meaning, "fill the gas tank"). We added it to the Conveyance parent class:

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
Can problems really arise in such a simple situation? In fact, they already have... Design pattern: Strategy - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
Our program now has a conveyance (a baby stroller) that does not fit nicely into the general concept. It could have pedals or be radio-controlled, but one thing is certain for sure — it won't have any place to pour in gas. Our class hierarchy has caused common methods to be inherited by classes that don't need them. What should we do in this situation? Well, we could override the fill() method in the Stroller class so that nothing happens when you try to refuel the stroller:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
But this can hardly be called a successful solution if for no other reason than duplicate code. For example, most of the classes will use the parent class's method, but the rest will be forced to override it. If we have 15 classes and we must override behavior in 5-6 of them, the code duplication will become quite extensive. Maybe interfaces can help us? For example, like this:

public interface Fillable {
  
   public void fill();
}
We'll create a Fillable interface with one fill()method. Then, those conveyances that need to be refueled will implement this interface, while other conveyances (for example, our baby stroller) will not. But this option doesn't suit us. In the future, our class hierarchy may grow to become very large (just imagine how many different types of conveyances there are in the world). We abandoned the previous version involving inheritance, because we don't want to override the fill() method many, many times. Now we have to implement it in every class! And what if we have 50? And if frequent changes will be made in our program (and this is almost always true for real programs!), we would have to rush through all 50 classes and manually change the behavior of each of them. So what, in the end, should we do in this situation? To solve our problem, we'll choose a different way. Namely, we'll separate our class's behavior from the class itself. What does that mean? As you know, every object has state (a set of data) and behavior (a set of methods). Our conveyance class's behavior consists of three methods: go(), stop() and fill(). The first two methods are fine just as they are. But we will move the third method out of the Conveyance class. This will separate the behavior from the class (more accurately, it will separate only part of the behavior, since the first two methods will remain where they are). So where should we put our fill() method? Nothing comes to mind :/ It seems like it's exactly where it should be. We'll move it to a separate interface: FillStrategy!

public interface FillStrategy {

   public void fill();
}
Why do we need such an interface? It's all straightforward. Now we can create several classes that implement this interface:

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
We created three behavioral strategies: one for ordinary cars, one for hybrids, and one for Formula 1 race cars. Each strategy implements a different refueling algorithm. In our case, we simply display a string on the console, but each method could contain some complex logic. What do we do next?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
We use our FillStrategy interface as a field in the Conveyance parent class. Note that we aren't indicating a specific implementation — we're using an interface. The car classes will need specific implementations of the FillStrategy interface:

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

Let's look at what we got!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Console output:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Great! The refueling process works as it should! By the way, nothing prevents us from using the strategy as a parameter in the constructor! For example, like this:

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

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

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Let's run our main() method (which remains unchanged). We get the same result! Console output:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
The strategy design pattern defines a family of algorithms, encapsulates each of them, and ensures that they are interchangeable. It lets you modify the algorithms regardless of how they are used by the client (this definition, taken from the book "Head First Design Patterns", seems excellent to me). Design pattern: Strategy - 4We already specified the family of algorithms we are interested in (ways to refuel cars) in separate interfaces with different implementations. We separated them from the car itself. Now if we need to make any changes to a particular refueling algorithm, it won't affect our car classes in any way. And to achieve interchangeability, we just need to add a single setter method to our Conveyance class:

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

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

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Now we can change strategies on the fly:

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
If baby strollers suddenly start running on gasoline, our program will be ready to handle this scenario :) And that's about it! You've learned one more design pattern that will undoubtedly be essential and helpful when working on real projects :) Until next time!
Comments (2)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Andrei Level 41
19 May 2021
Nice, great example!
KIN SOCHEAT Level 34, Phnom Penh, Cambogia
18 July 2020
nice Thanks