[design mode from introduction to mastery] 21 - strategy mode

Posted by txmedic03 on Mon, 17 Jan 2022 18:25:21 +0100

Note source: Shang Silicon Valley Java design pattern (illustration + framework source code analysis)

Strategy mode

1. Duck problem

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

  • 1) There are ducks (such as wild ducks, Peking ducks, water ducks, etc. ducks have various behaviors, such as barking, flying, etc.)
  • 2) Display duck information

2. Traditional solutions to duck problems

UML class diagram

Core code

public abstract class Duck {
    public void quark() {
        System.out.println("Ducks quack~");
    }

    public void swim() {
        System.out.println("Ducks swims~");
    }

    public void fly() {
        System.out.println("Ducks soar~");
    }

    public abstract void display();
}
public class WildDuck extends Duck {

    @Override
    public void display() {
        System.out.println("Wild duck");
    }
}
public class PekingDuck extends Duck {
    @Override
    public void display() {
        System.out.println("Peking duck~");
    }

    @Override
    public void fly() {
        System.out.println("Peking duck can't fly~");
    }
}
public class ToyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("Toy duck~");
    }

    @Override
    public void quark() {
        System.out.println("Toy ducks don't bark~");
    }

    @Override
    public void swim() {
        System.out.println("Toy ducks can't swim~");
    }

    @Override
    public void fly() {
        System.out.println("Toy ducks can't fly~");
    }
}

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 mentioned above is actually caused by inheritance: local changes to classes, especially super classes, will affect other parts and have spillover effects
  • 3) In order to improve the problem, we can solve it by overriding fly = > overriding
  • 4) The problem comes again. If we have a toy Duck ToyDuck, we need ToyDuck to cover all the implementation methods of Duck = > solution: policy mode

3. Basic introduction to policy mode

  • 1) In Strategy Pattern, algorithm families are defined and encapsulated respectively so that they can be replaced with each other. This mode makes the change of the algorithm independent of the customer 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)

Schematic class diagram

Note: as can be seen from the above figure, the customer Context has a member variable Strategy or other policy interfaces. You can specify which policy to use in the constructor

4. Strategy mode to solve duck problem

  • 1) Application example requirements: write the program to complete the previous duck project, and use the policy mode
  • 2) Train of thought analysis
    • 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
  • 3) Code implementation

UML class diagram

Core code

The act of "shouting"

/**
 * "Behavior policy interface
 */
public interface QuarkBehavior {
    void quark();
}
/**
 * "Do not call behavior policy object
 */
public class NoQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("Can't call~");
    }
}
/**
 * "Rattle behavior policy object
 */
public class GagaQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("Quack~");
    }
}
/**
 * "Cackle behavior policy object
 */
public class GegeQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("Cackle~");
    }
}

"Swimming" behavior

/**
 * "Swimming behavior policy interface
 */
public interface SwimBehavior {
    void swim();
}
/**
 * "Can't swim behavior policy object
 */
public class NoSwimHehavior implements SwimBehavior {
    @Override
    public void swim() {
        System.out.println("Unable to swim~");
    }
}
/**
 * "Swimming behavior policy object
 */
public class CanSwimHehavior implements SwimBehavior {
    @Override
    public void swim() {
        System.out.println("Can swim~");
    }
}

"Flying" behavior

/**
 * "Flight behavior policy interface
 */
public interface FlyBehavior {
    void fly();
}
/**
 * "Can't fly behavior policy object
 */
public class NoFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("Can't fly~");
    }
}
/**
 * "Less able to fly behavior policy object
 */
public class BadFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("Not very good at flying~");
    }
}
/**
 * "Flying behavior policy object
 */
public class GoodFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("Very good at flying~");
    }
}

Ducks

/**
 * Abstract duck class
 */
public abstract class Duck {
    protected QuarkBehavior quarkBehavior;
    protected SwimBehavior swimBehavior;
    protected FlyBehavior flyBehavior;

    public Duck() {
        display();
    }

    public void quark() {
        if (quarkBehavior != null) {
            quarkBehavior.quark();
        }
    }

    public void swim() {
        if (swimBehavior != null) {
            swimBehavior.swim();
        }
    }

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

    public void setQuarkBehavior(QuarkBehavior quarkBehavior) {
        this.quarkBehavior = quarkBehavior;
    }

    public void setSwimBehavior(SwimBehavior swimBehavior) {
        this.swimBehavior = swimBehavior;
    }

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

    public abstract void display();
}
/**
 * Wild duck
 */
public class WildDuck extends Duck {
    public WildDuck() {
        super();
        quarkBehavior = new GegeQuarkBehavior();
        swimBehavior = new CanSwimHehavior();
        flyBehavior = new GoodFlyBehavior();
    }

    @Override
    public void display() {
        System.out.println("======Wild duck======");
    }
}
/**
 * Peking duck
 */
public class PekingDuck extends Duck {
    public PekingDuck() {
        super();
        quarkBehavior = new GagaQuarkBehavior();
        swimBehavior = new CanSwimHehavior();
        flyBehavior = new BadFlyBehavior();
    }

    @Override
    public void display() {
        System.out.println("======Peking duck======");
    }
}
/**
 * Toy duck
 */
public class ToyDuck extends Duck {
    public ToyDuck() {
        super();
        quarkBehavior = new NoQuarkBehavior();
        swimBehavior = new NoSwimHehavior();
        flyBehavior = new NoFlyBehavior();
    }

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

Test code

Duck wildDuck = new WildDuck();
wildDuck.quark();
wildDuck.swim();
wildDuck.fly();

Duck pekingDuck = new PekingDuck();
pekingDuck.quark();
pekingDuck.swim();
pekingDuck.fly();
System.out.println("===shuffle the cards===");
pekingDuck.setFlyBehavior(new NoFlyBehavior());
pekingDuck.fly();

Duck toyDuck = new ToyDuck();
toyDuck.quark();
toyDuck.swim();
toyDuck.fly();

test result

//======Wild duck======
//Cackle~
//Can swim~
//Very good at flying~
//======Beijing Duck======
//Quack~
//Can swim~
//Not very good at flying~
//===Change strategy===
//Can't fly~
//======Toy duck======
//Can't call~
//Can't swim~
//Can't fly~

5. Source code analysis of policy pattern in JDK arrays application

The Comparator of JDK's Arrays uses the policy mode

  • The anonymous class object new Comparator < integer > () {} implements the Comparator interface (policy interface)
  • public int compare(Integer o1, Integer o2) {} specifies the specific processing method
Comparator<Integer> comparator = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 > o2 ? 1 : -1;
    }
};
// Mode 1
Arrays.sort(data, comparator);
System.out.println(Arrays.toString(data));
// [1, 2, 3, 4, 8, 9]

//Mode 2
Arrays.sort(data, (v1, v2) -> v1.compareTo(v2) > 0 ? -1 : 1);
System.out.println(Arrays.toString(data));
//[9, 8, 4, 3, 2, 1]

6. 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; It is more flexible to use behavior class composition instead of behavior inheritance
  • 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 be huge

Topics: Design Pattern