In the usual coding process, we often encounter a variety of implementations of an interface, different implementations (different strategies) used in different scenarios. For a simple scenario, intra-site messages used in a project can be stored in a relational database (by different vendors) or a non-relational database. New inserts a message such as this way, he could be a lot of the implementation of the way, as our reality in order to meet different customers, relational database using mysql, oracel some customers are using mongonDB non-relational database, customer according to their own requirements, we dynamically adjust the switch, without the need to develop.
It’s not just code, for example, every year on Singles’ Day, we buy one product; In recent years, businesses have made a wide variety of preferential programs for you to choose from. The combination of coupons, what cross store full reduction, deposit against cash, become a member of the store to enjoy a few discounts and so on, often we will consider a comprehensive way to test the most money, we will be inclined to a certain way. The strategy mode introduced today can solve these problems in our life or software design.
Definition and structural characteristics
The full name of Strategy Pattern is Strategy Design Pattern. In GoF’s Book Design Patterns, it is defined as follows: define a family of algorithm classes, encapsulate each algorithm individually, and make them interchangeable. Policy patterns can make changes to algorithms independent of the clients that use them (by client I mean the code that uses the algorithm). Corresponding to our previous example is our message write implementation. The policy pattern belongs to the object behavior pattern. It encapsulates the algorithm, separates the responsibility of using the algorithm from the implementation of the algorithm, and assigns different objects to manage these algorithms.
The structure and implementation of policy patterns
The policy pattern is to prepare a set of algorithms and encapsulate this set of algorithms into a series of policy classes as a subclass of an abstract policy class. The focus of the strategy pattern is not how to implement the algorithm, but how to organize the algorithm, so as to make the program structure more flexible, with better maintainability and scalability, now let’s analyze its basic structure and implementation method.
- Pattern structure
- Abstract Strategy class: Defines a common interface that is implemented in different ways by different algorithms, and is used by environment actors to invoke different algorithms, typically implemented using interfaces or abstract classes.
- Concrete Strategy class: Interfaces that implement abstract policy definitions and provide Concrete algorithm implementations.
- Context class: Holds a reference to a policy class that is ultimately called by the client.
2. Structure diagram of policy pattern
Code case
1. Case 1
- Abstract strategy
public interface AbstractStrategy {
// Can be an abstract method of an interface or an abstract class
void operation(a);
}
Copy the code
- Specific Strategy 1
public class ConcreteStrategy1 implements AbstractStrategy{
@Override
public void operation(a) {
System.out.println("Strategy 1 Implementation"); }}Copy the code
- Specific Strategy 2
public class ConcreteStrategy2 implements AbstractStrategy{
@Override
public void operation(a) {
System.out.println("Implementation of Strategy 2"); }}Copy the code
- The policy environment
public class Context {
private AbstractStrategy strategy;
public AbstractStrategy getStrategy(a) {
return strategy;
}
public void setStrategy(AbstractStrategy strategy) {
this.strategy = strategy;
}
public void doStrategy(a){ strategy.operation(); }}Copy the code
- The client
public class Client {
public static void main(String[] args) {
ConcreteStrategy1 concreteStrategy1 = new ConcreteStrategy1();
// Set the specified policy
Context context = new Context();
context.setStrategy(concreteStrategy1);
/ / call
context.doStrategy();
System.out.println("------- reset --------");
ConcreteStrategy2 concreteStrategy2 = newConcreteStrategy2(); context.setStrategy(concreteStrategy2); context.doStrategy(); }}Copy the code
Execution Result:
------- Reconfigure -------- policy 2Copy the code
The implementation of policies can be switched by setting different policy implementations in the context of the environment, corresponding to the previous concept definition: define a family of algorithm classes, and encapsulate each algorithm separately so that they can be replaced with each other. Give it a try
2. Case 2
Case 2, let’s take an example of a real situation, let’s say we need to make a shrimp. Everyone has different tastes and may order different recipes when eating. Here we take the chef’s way of cooking prawns as an abstraction of strategy. The different approaches are the concrete implementation of the abstract strategy (different algorithms).
- Abstract strategy
// Abstract policy
public interface AbstractShrimpCook {
// Prawn cooking style
void cook(a);
}
Copy the code
- Concrete implementation A
public class BraiseShrimp implements AbstractShrimpCook{
@Override
public void cook(a) {
System.out.println("Braised prawns..."); }}Copy the code
- Concrete implementation B
public class PoachShrimp implements AbstractShrimpCook {
@Override
public void cook(a) {
System.out.println("Boiled prawns..."); }}Copy the code
- Concrete implementation C
/ / steamed
public class SteamedShrimp implements AbstractShrimpCook {
public void cook(a) {
System.out.println("Steamed prawns..."); }}Copy the code
- Environmental information
public class Context {
AbstractShrimpCook abstractShrimpCook;
public AbstractShrimpCook getAbstractShrimpCook(a) {
return abstractShrimpCook;
}
public void setAbstractShrimpCook(AbstractShrimpCook abstractShrimpCook) {
this.abstractShrimpCook = abstractShrimpCook;
}
/ / call
public void cook(a) { abstractShrimpCook.cook(); }}Copy the code
- The client
public class Client {
public static void main(String[] args) throws Exception {
/ / boiled
String cookType = "POACH";
AbstractShrimpCook abstractShrimpCook;
if ("POACH".equals(cookType)) {
abstractShrimpCook = new PoachShrimp();
}
else if ("BRAISE".equals(cookType)) {
abstractShrimpCook = new BraiseShrimp();
}
else if ("STEAMED".equals(cookType)) {
abstractShrimpCook = new SteamedShrimp();
} else {
throw new Exception("Policy of specified type not found");
}
Context context = newContext(); context.setAbstractShrimpCook(abstractShrimpCook); context.cook(); }}Copy the code
Here we simulate the caller of the program to get the concrete policy, the concrete implementation algorithm of the abstract policy, according to the specified type passed.
There is a serious problem here, and as a user, we can see that there is no need to pay attention to how the policy is implemented. The specific policy used is determined by the type of policy executed. If a new implementation is added, the user also needs to add new ifelse judgment logic. This also defeats the essence of the strategic pattern. Instead of focusing on the implementation of policies, focus on how to organize these algorithms so that the application structure is more flexible, maintainable, and extensible.
Let’s change case 2; Change the context as follows and add a cooking type enumeration object
- The enumeration object
public enum CookType {
POACH,
BRAISE,
STEAMED;
}
Copy the code
- context
public class Kitchen {
AbstractShrimpCook abstractShrimpCook;
// Policy cache
static final Map<CookType,AbstractShrimpCook> cookMap = new HashMap<CookType, AbstractShrimpCook>();
static {
cookMap.put(CookType.POACH, new PoachShrimp());
cookMap.put(CookType.BRAISE, new BraiseShrimp());
cookMap.put(CookType.STEAMED, new SteamedShrimp());
}
public AbstractShrimpCook getAbstractShrimpCook(a) {
return abstractShrimpCook;
}
public void setAbstractShrimpCook(AbstractShrimpCook abstractShrimpCook) {
this.abstractShrimpCook = abstractShrimpCook;
}
// Invoke specific policy methods
public void cookMethod(a) {
abstractShrimpCook.cook();
}
// Match the policy in the cache by type
public void cookMethod2(CookType cookStrategy){
AbstractShrimpCook cShrimpCook = cookMap.get(cookStrategy);
if (Objects.nonNull(cShrimpCook)) {
cShrimpCook.cook();
}else {
throw new IllegalArgumentException("this "+ cookStrategy +"cookStrategy not found."); }}}Copy the code
- Client 2
public class Client {
public static void main(String[] args) {
// Initialize the kitchen.
Kitchen kitchen = new Kitchen();
// Initialize the cooking strategy (mode)
PoachShrimp poachShrimp = new PoachShrimp();
kitchen.setAbstractShrimpCook(poachShrimp);
/ / to make
poachShrimp.cook();
System.out.println("------ mode 2 ------"); kitchen.cookMethod2(CookType.BRAISE); }}Copy the code
Boiled prawns…
—— way 2 —— braised prawns…
Case Structure Diagram:
The difference between this method and method 1 is that context (Kitchen) maintains the implementation of all algorithms, and the client does not need to pay attention to the specific implementation, passing the specified type when using it. In case of further expansion, the user only needs to abstract the implementation of the strategy and add it to the context, without changing the code logic. Moreover, the implementation and use of the algorithm are isolated, meeting the open and closed principle. Policy adding a client Does not require adding new logic.
Advantages and disadvantages of policy mode and application scenarios
1. Advantages:
- Multi-conditional statements are difficult to maintain, and using policy patterns can avoid multi-conditional statements, such as if… Else statement, switch… A case statement.
- The policy pattern provides a family of algorithms that can be reused, and inheritance can be used appropriately to move the common code of the algorithm family into the parent class to avoid duplicate code.
- The policy pattern can provide different implementations of the same behavior, and customers can dynamically switch implementations according to different requirements.
- The policy pattern provides perfect support for the open closed principle, allowing for the flexibility of adding new algorithms without modifying the original code.
- The policy pattern separates the use of the algorithm into the environment class and the implementation of the algorithm into the concrete policy class.
2. Disadvantages:
- The client must understand the differences between all the policy algorithms in order to select the right algorithm class at the right time.
- The policy mode creates many policy classes, which increases the maintenance difficulty.
Application scenarios of the policy mode
- The system has multiple implementations of an algorithm (method), and the need for dynamic switching algorithm scene;
- Algorithms in the system are completely independent of each other, and the implementation details of specific algorithms are required to be hidden from customers;
- Multiple classes differ only in their behavior, and you can use the policy pattern to dynamically select the specific behavior to be performed at run time.
Use of policy patterns in source code
- Use of JDK source code
In the JDK, the Comparator interface provides the compare () abstraction for defining collation rules. Implementation classes can customize their comparison algorithms for sorting.
public static void main(String[] args) {
// Define an integer array
Integer[] array = {3.5.7.2.8.1.9};
// Define the ascending anonymous implementation algorithm
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2) {
return -1;
} else {
return 0; }}};// Use the Arrays utility class to sortArrays.sort(array,comparator); System.out.println(Arrays.toString(array)); }}Copy the code
Execution result [1, 2, 3, 5, 7, 8, 9]
Take a look at the source code for the arrays.sort () method in the JDK
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); }}Copy the code
Default If you do not upload the sorting algorithm, you can use the default sorting rule (ascending) or customize the sorting rule.
Here is our custom implementation of the ascending algorithm. In JDK collections, constructors often pass our sorting algorithm. Such as TreeMap
public class TreeMap<K.V> extends AbstractMap<K.V> implements NavigableMap<K.V>, Cloneable.java.io.Serializable {
// Pass the Comparator sorting algorithm. The values stored in the map are sorted according to the sorting algorithm
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator; }}Copy the code
We know that treeMap is ordered, and we can customize the sorting strategy through the sorting algorithm of the constructor. There are such as TreeSet collection, source code as follows
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable.java.io.Serializable
{
public TreeSet(Comparator<? super E> comparator) {
this(newTreeMap<>(comparator)); }}Copy the code