JavaRush /Java Blog /Random EN /Design pattern “Strategy”

Design pattern “Strategy”

Published in the Random EN group
Hello! In previous lectures, we have already encountered the concept of “design pattern”. In case you forgot, let us remind you: this term denotes a certain standard solution to a common problem in programming. Design pattern “Strategy” - 1At JavaRush we often say that the answer to almost any question can be Googled. Therefore, someone has probably already successfully solved a problem similar to yours. So, patterns are time-tested and practice-tested solutions to the most common problems or methods for resolving problem situations. These are the very “bicycles” that in no case do you need to invent yourself, but you need to know how and when to apply them :) Another task of patterns is to bring architecture to a single standard. Reading someone else's code is not an easy task! Everyone writes it differently, because the same problem can be solved in many ways. But the use of patterns allows different programmers to understand the logic of the program without delving into every line of code (even if they see it for the first time!) Today we will look at one of the most common patterns called “Strategy”. Design pattern “Strategy” - 2Let's imagine that we are writing a program that actively works with the Car object. In this case, it’s not even particularly important what exactly our program does. To do this, we created an inheritance system with one parent class Autoand three child classes: Sedan, Truckand F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
All three child classes inherit two standard methods from the parent - gas()and stop() our program is completely simple: cars can only drive forward and brake. Continuing our work, we decided to add a new method to cars - fill()(refuel). Let's add it to the parent class Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
It would seem that problems could arise in such a simple situation? Well, in fact, they have already arisen... Design Pattern “Strategy” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
A car has appeared in our program that does not fit into the general concept - a children's buggy. It may be pedal-powered or radio-controlled, but one thing is for sure - there is nowhere to put gasoline into it. Our inheritance scheme has resulted in us giving away common methods even to classes that don't need them. What should we do in such a situation? Well, for example, you can override the method fill()in the class ChildrenBuggiesso that when you try to refuel the buggy, nothing happens:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
But this solution can hardly be called successful, at least because of code duplication. For example, most classes will use a method from the parent class, but other classes will be forced to override it. If we have 15 classes, and in 5-6 we are forced to override behavior, the code duplication will become quite extensive. Maybe interfaces can help us? For example, this one:
public interface Fillable {

   public void fill();
}
We will create an interface Fillablewith one method fill(). Accordingly, those cars that need to be refueled will implement this interface, but other cars (for example, our buggy) will not. But this option will not suit us either. Our class hierarchy may grow to a very large number in the future (imagine how many different types of cars there are in the world). We abandoned the previous inheritance option because we didn't want to override the fill(). Here we will have to implement it in every class! What if we have 50 of them? And if frequent changes are made to our program (and in real programs this will almost always be the case!), we will have to run around with our tongue hanging out between all 50 classes and change the behavior of each of them manually. So what should we do in the end? To solve our problem, let's choose a different path. Namely, let's separate the behavior of our class from the class itself. What does it mean? As you know, any object has a state (a set of data) and a behavior (a set of methods). The behavior of our machine class consists of three methods - gas(), stop()and fill(). The first two methods are fine. But we will move the third method outside the class Auto. This will be the separation of behavior from the class (more precisely, we separate only part of the behavior - the first two methods remain in place). Where should we move our method fill()? Nothing immediately comes to mind :/ He seemed to be completely in his place. We will move it to a separate interface - FillStrategy!
public interface FillStrategy {

   public void fill();
}
Why do we need this interface? It's simple. Now we can create several classes that will implement this interface:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
We have created three behavior strategies - for conventional cars, for hybrids and for Formula 1 cars. Each strategy implements a separate refueling algorithm. In our case, this is just output to the console, but there may be some complex logic inside the method. What should we do with this next?
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
We use our interface FillStrategyas a field in the parent class Auto. Please note: we do not specify a specific implementation, but rather use the interface. And we will need specific implementations of the interface FillStrategyin the child car classes:
public class F1Car extends Auto {

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

public class HybridAuto extends Auto {

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

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
Let's see what we got:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

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

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
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 Auto {

   private FillStrategy fillStrategy;

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

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

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



public class HybridAuto extends Auto {

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

public class F1Car extends Auto {

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

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
The Strategy pattern defines a family of algorithms, encapsulates each of them, and ensures that they are interchangeable. It allows you to modify algorithms regardless of their use on the client side (this definition is taken from the book “Exploring Design Patterns” and seems to me extremely successful). Design Pattern “Strategy” - 4We have isolated the family of algorithms that interest us (types of refueling cars) into separate interfaces with several implementations. We have separated them from the very essence of the car. Therefore, now, if we need to make any changes to this or that refueling process, this will not affect our classes of cars in any way. As for interchangeability, to achieve it we just need to add one setter method to our class Auto:
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   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) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
If suddenly children's buggy cars start to be filled with gasoline, our program will be ready for such a scenario :) That's all, actually! You've learned another design pattern, which you will undoubtedly need and will help you out more than once when working on real projects :) See you again!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION