Design pattern - strategy pattern

Posted by mynameisbob on Sat, 15 Jan 2022 11:43:51 +0100

Prepare the duck project, and the specific requirements are as follows:

  1. There are all kinds of ducks (such as wild duck, Peking duck, water duck, etc. ducks have all kinds of behaviors, such as barking, flying, etc.)

  2. Display duck information

1. Analysis and code implementation of traditional solutions to duck problems

Traditional design scheme (class diagram)

Code implementation:

  • Duck

    public abstract class Duck {
    
        public Duck() {
    
        }
        public abstract void display();//Display duck information
        public void quack() {
            System.out.println("Ducks quack~~");
        }
    
        public void swim() {
            System.out.println("Ducks can swim~~");
        }
    
        public void fly() {
            System.out.println("Ducks can fly~~~");
        }
    
    }
    
  • PekingDuck

    public class PekingDuck extends Duck {
    
        @Override
        public void display() {
            System.out.println("~~Peking duck~~~");
        }
    
        //Because Beijing ducks can't fly, they need to rewrite fly 
        @Override
        public void fly() {
            System.out.println("Peking duck can't fly");
        }
    }
    
  • ToyDuck

    public class ToyDuck extends Duck{
    
        @Override
        public void display() {
            System.out.println("Toy duck");
        }
        
        //All methods of the parent class need to be overridden 
        public void quack() {
            System.out.println("Toy ducks can't bark~~");
        }
    
        public void swim() {
            System.out.println("Toy ducks can't swim~~");
        }
    
        public void fly() {
            System.out.println("Toy ducks can't fly~~~");
        }
    }
    
  • WildDuck

    public class WildDuck extends Duck {
    
        @Override
        public void display() {
            System.out.println(" This is a wild duck ");
        }
    }
    

Traditional problem analysis and solution

  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. There will be spillover effects
  3. In order to improve the 1 problem, we can solve it by covering fly = > covering
  4. The problem comes again. If we have a toy Duck ToyDuck, we need ToyDuck to cover all the implementation methods of Duck = > solution ideas = > strategy pattern

1, Strategy mode

1. Basic introduction

  1. In the Strategy Pattern, algorithm families (policy groups) are defined and encapsulated respectively so that they can be replaced with each other. This pattern makes the changes of the algorithm independent of the customers using the algorithm
  2. 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).

2. Schematic class diagram of strategy mode

Note: as can be seen from the above figure, the customer context has a member variable strategy or other policy interfaces. As for which policy to use, we can specify it in the constructor

3. Strategic mode to solve the problem

  1. Application example requirements

    Writing a program to complete the previous duck project requires the use of policy mode

  2. Train of thought analysis (class diagram)

    Policy mode: encapsulate the behavior interface, implement the algorithm family, put the behavior interface object in the superclass, and set the behavior object in the subclass. The principle is to separate the changing parts, encapsulate the interface, and program various functions based on the interface. This mode makes the change of behavior independent of the user of the algorithm

Code implementation:

  • BadFlyBehavior

    public interface FlyBehavior {
    
            void fly(); //  Subclass concrete implementation
    }
    
  • GoodFlyBehavior

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

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

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

    public abstract class Duck {
    
        //Properties, policy interface
        FlyBehavior flyBehavior;
    
        public Duck() {
        }
        
        public abstract void display();//Display duck information
        
        public void quack() {
            System.out.println("Ducks quack~~");
        }
    
        public void swim() {
            System.out.println("Ducks can swim~~");
        }
    
        public void fly() {
            //improvement
            if(flyBehavior != null) { 
                flyBehavior.fly();
            }
        }
    
        public void setFlyBehavior(FlyBehavior flyBehavior) {
            this.flyBehavior = flyBehavior;
        }
    }
    
  • PekingDuck

    public class PekingDuck extends Duck {
        
        //If Beijing ducks can fly, but their flying skills are average 
        public PekingDuck() {
            flyBehavior = new BadFlyBehavior();
    
        }
    
        @Override
        public void display() {
            System.out.println("~~Peking duck~~~");
        }
    }
    
  • ToyDuck

    public class ToyDuck extends Duck{
    
        public ToyDuck() {
            flyBehavior = new NoFlyBehavior();
        }
    
        @Override
        public void display() {
            System.out.println("Toy duck");
        }
    
        //All methods of the parent class need to be overridden 
        public void quack() {
            System.out.println("Toy ducks can't bark~~");
        }
        
        public void swim() {
            System.out.println("Toy ducks can't swim~~");
        }
    }
    
  • WildDuck

    public class WildDuck extends Duck {
    
        //Constructor, object passed in FlyBehavor
        public	WildDuck() {
            flyBehavior = new GoodFlyBehavior();
        }
    
        @Override
        public void display() {
            System.out.println(" This is a wild duck ");
        }
    }
    

2, Source code analysis of policy pattern in JDK arrays application

  1. The Comparator of JDK's Arrays uses the policy mode

  2. Code analysis + Debug source code + pattern role analysis

    • Arrays

      public class Arrays {
          
          // The Comparator interface uses the policy pattern
          public static <T> void sort(T[] a, Comparator<? super T> c) {
              if (c == null) {
                  sort(a);
              } else {
                  if (LegacyMergeSort.userRequested)
                      legacyMergeSort(a, c);
                  else
                      TimSort.sort(a, 0, a.length, c, null, 0, 0);
              }
          }
          
      }
      
    • ArraysStrategy

      public class Strategy {
      
          public static void main(String[] args) {
              //array
              Integer[] data = { 9, 1, 2, 8, 4, 3 };
              // Realize descending sorting, return - 1 on the left, 1 on the right, and 0 remains unchanged
              // explain
              // 1. Implement Comparator interface (policy interface), anonymous class object new Comparator < integer > () {..}
              // 2. Object new comparator < integer > () {..} Is the object that implements the policy interface
              // 3. public int compare(Integer o1, Integer o2) {} specifies the specific processing method
              Comparator<Integer> comparator = new Comparator<Integer>() { 
                  public int compare(Integer o1, Integer o2) {
                      if (o1 > o2) {
                          return -1;
                      } else {
                          return 1;
                      }
                  };
              };
      
              // explain
              /*
              public static <T> void sort(T[] a, Comparator<? super T> c) { 
                  if (c == null) {
                      sort(a); //Default method
                  } else {
                      if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); //Using Policy Object c
                      else
                          // Using Policy Object c
                          TimSort.sort(a, 0, a.length, c, null, 0, 0);
                  }
              }
      		*/
              //Mode 1
              Arrays.sort(data, comparator);
              System.out.println(Arrays.toString(data)); // Descending sort
              
              //Mode 2 - implement the policy pattern simultaneously with lambda expressions
              Integer[] data2 = { 19, 11, 12, 18, 14, 13 };
      
              Arrays.sort(data2, (var1, var2) -> { 
                  if(var1.compareTo(var2) > 0) {
                      return -1;
                  } else {
                      return 1;
                  }
              });
              System.out.println("data2=" + Arrays.toString(data2));
          }
      

3, 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 can add behavior without modifying the original code. Just add a strategy (or behavior), avoiding the use of multiple transfer statements (if... else, if... else)
  4. It provides a way to replace the inheritance relationship: 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 increase

Topics: Design Pattern