Strategy pattern

Posted by matt6805 on Fri, 21 Jan 2022 20:08:14 +0100

Duck problem

​  1. There are all kinds of ducks, eg: wild duck, Peking duck, water duck

​  2. Ducks have various behaviors: barking and flying

​  3. Display duck information

 

Traditional scheme

Duck
	wild duck
	Peking duck
	Water duck

Problem analysis and solutions implemented in traditional ways

 1. Other ducks inherit the Duck class, so fly makes all subclasses fly, which is incorrect

 2. The problem of 1 mentioned above is actually caused by inheritance: local changes to classes, especially super classes, will affect other parts and easily cause overflow effects.

 3. In order to improve the 1 problem, we can solve it by covering fly = > covering

 

Strategy mode

  define algorithm families, and package them into separate classes, so that they can replace each other. This mode makes the changes of algorithms independent of the customers using algorithms

  this algorithm embodies several design principles. First, separate the changed code from the unchanged code; Second, programming for interfaces rather than specific classes (policy interfaces are defined); Third, use more combination aggregation and less inheritance (customers use strategies through combination)

 

Strategy pattern structure

 1. Context maintains a reference to a specific policy and communicates with the object only through the policy interface.

 2. The Strategy interface is a common interface for all specific policies. It declares a method used by the context to execute the policy.

 3. Concrete Strategies implement various variants of the algorithms used in the context.

 4. The Client creates a specific policy object and passes it to the context. The context provides a setter so that the Client can replace the associated policy at run time.

When the context needs to run the algorithm, it calls the execution method on its connected policy object. The context is unclear about the type of policy involved and the execution of the algorithm.

 

The policy mode is suitable for application scenarios

 1. When you want to use various algorithm variants in the object and want to switch algorithms at run time, you can use the policy mode.

 2. Use the policy pattern when you have many similar classes that are only slightly different when performing certain behaviors.

 3. If the algorithm is not particularly important in the logic of the context, using this pattern can isolate the business logic of the class from the details of its algorithm implementation.

 4. This pattern can be used when a complex conditional operator is used in a class to switch between different variants of the same algorithm.

 

Implementation mode

  1. Find the algorithm with high modification frequency from the context class (it may also be a complex conditional operator used to select an algorithm variant at run time).
  2. Declare a common policy interface for all variants of the algorithm.
  3. The algorithms are extracted into their respective classes one by one, and they must implement the policy interface.
  4. Add a member variable in the context class to hold the reference to the policy object. Then provide a setter to modify the member variable. The context can only interact with the policy object through the policy interface. If necessary, an interface can be defined to allow the policy to access its data.
  5. The client must associate the context class with the corresponding policy so that the context can complete its main work in the expected way.

 

Advantages and disadvantages of strategy mode

  advantages:

   ✔️ You can switch algorithms within objects at run time.

   ✔️ You can isolate the implementation of the algorithm from the code that uses the algorithm.

   ✔️ You can use composition instead of inheritance.

   ✔️ Opening and closing principle. You can introduce new policies without modifying the context.

  disadvantages:

   ❌ If your algorithm rarely changes, there is no reason to introduce new classes and interfaces. Using this mode will only make the program too complex.

   ❌ The client must be aware of the differences between policies -- it needs to choose the appropriate policy.

   ❌ Many modern programming languages support function types, allowing you to implement different versions of algorithms in a set of anonymous functions. In this way, you use these functions in exactly the same way as you use policy objects, without the need for additional classes and interfaces to keep the code concise.

 

Solve the duck problem

code

//Flight interface
public interface FlyBehavior {
    //Flight method
    void fly();
}

public class BadFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("General flying skills");
    }
}

public class GoodFlyBehavior implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("Superb flying skills");
    }
}

public class NoFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("Can't fly");
    }
}
public interface QuackBehavior {
    void quack();
}

public class GaGaQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Ga~Ga~");
    }
}

public class NoQuackBehavior implements QuackBehavior{

    @Override
    public void quack() {
        System.out.println("The duck can't bark");
    }
}
public abstract class Duck {
    //Policy interface
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public abstract void display();//Display duck information

    public void quack(){
        quackBehavior.quack();
    }

    public void fly(){
        if (flyBehavior != null)
            flyBehavior.fly();
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}

//wild duck
public class WildDuck extends Duck{
    public WildDuck() {
        //Good flying ability, GaGa
        flyBehavior = new GoodFlyBehavior();
        quackBehavior = new GaGaQuackBehavior();
    }

    @Override
    public void display() {
        System.out.println("wild duck");
    }
}

//Toy duck
public class ToyDuck extends Duck{
    public ToyDuck() {
        //Can't fly or scream
        flyBehavior = new NoFlyBehavior();
        quackBehavior = new NoQuackBehavior();
    }

    @Override
    public void display() {
        System.out.println("Toy duck");
    }
}


//Peking duck
public class PekingDuck extends Duck{
    public PekingDuck(){
        //Average flying ability, GaGa called
        flyBehavior = new BadFlyBehavior();
        quackBehavior = new GaGaQuackBehavior();
    }

    @Override
    public void display() {
        System.out.println("Peking duck");
    }
}
public class Client {
    public static void main(String[] args) {
        Duck wildDuck = new WildDuck();
        wildDuck.display();
        wildDuck.fly();
        wildDuck.quack();

        Duck toyDuck = new ToyDuck();
        toyDuck.display();
        toyDuck.fly();
        toyDuck.quack();
    }
}

 
Considerations and details of policy mode
 1. The key of the strategy model is to analyze the changing part and the unchanged part of the project

 2. The core idea of the strategy model is: use more combination aggregation and less inheritance; Use behavior class composition instead of behavior inheritance. More elastic

 3. It embodies the principle of "close to modification and open to extension". The client adds behavior without modifying the original code. As long as a strategy (or behavior) is added, the use of multiple transfer statements (if... else, if... else) can be avoided

 4. The policy pattern encapsulates the algorithm in an independent Strategy class, so that you can change it independently of its Context, making it easy to switch, understand and expand

 5. It should be noted that every time you add a policy, you need to add a class. When there are too many policies, the number of classes will be huge