Introduction to policy mode and its specific usage scenarios

Keywords: Java Design Pattern

preface

Today, I optimized the previous business code with the strategy mode and reviewed the relevant concepts of the design mode. I'll record it here

1, Introduction to policy mode

definition:

The policy pattern defines the algorithm family and encapsulates them separately so that they can replace each other. This pattern makes the changes of the algorithm not affect the customers using the algorithm. (from < big talk design mode >)

Personal understanding:

The simple understanding of the policy pattern should be to provide different implementation logic for the same business function under different scenario requirements to achieve the purpose of dynamically switching business algorithms and meeting different scenarios. At the same time, it also has other advantages, that is, it optimizes the code structure, separates it from a large number of logical judgments, only provides Context context, decouples the algorithm from the actual business code, and shields the underlying implementation logic from users.

The UML class diagram of policy pattern is as follows

2, Specific usage scenarios

  • 1. The business code needs to switch different implementation logic according to different scenarios
  • 2. There are a lot of if else logical judgments in the code

1. Examples

Here, take the business code scenario of our company as an example, and do not disclose the specific code. Take a similar example, as follows:

        Integer a = 1;
        if (a == 1) {
            // Business logic processing
        } else if (a == 2) {
            // Business logic processing
        } else if (a == 3) {
            // Business logic processing
        }  else if (a == 4) {
            // Business logic processing
        } else if (a == 5) {
            // Business logic processing
        } else if (a == 6) {
            // Business logic processing
        } else if (a == 7) {
            // Business logic processing
        } else {
            // Business logic processing
        }

When there are only two or three branches, it is not inappropriate to adopt the above treatment, but what if there are more branches? What about adding another implementation scheme next time?
At this time, the code becomes very cystic and makes people feel numb.

2. Traditional optimization

After analyzing the above code, it is found that there can be several optimization points:

  1. Too many if else branches
  2. Logic processing algorithm and business code coupling
  3. The new logic implementation cannot be extended gracefully

According to the UML class diagram of the policy pattern in the introduction, the traditional processing method is to encapsulate each logical processing into a class, and then expose the policy Context class to the caller for use.
One disadvantage of this method is that there are too many class codes. Each strategy needs to add an implementation class. After the amount of contemporary code is too large, it is not easy for latecomers to understand. There is also the branching problem of if else. After being handled in the traditional way, if else logical judgment is only migrated to the Context, but too many branches can not be avoided.

The traditional optimization code is not introduced here

3. Map + functional programming optimization

All policies are saved in a Map and implemented by obtaining the corresponding implementation method for the key. value is stored in the form of lambda function.

After optimization, there are two main classes:
One is the Context context, which is exposed to client calls, where all policies are maintained.
One is strategy impl, which provides the actual implementation of all policies. A policy is a method.

The Context code example is as follows

/**
 * Policy context
 *
 * @author linzp
 * @date 2021/11/12 11:14
 */
@Slf4j
@Component
public class XxxxStrategyContext {

    /**
     * Store all policies
     */
    private Map<String, BiFunction<String, String, String>> strategyMap = new HashMap<>(16);

    /**
     * Specific strategy details
     */
    @Autowired
    private XxxxStrategyImpl xxxStrategy;

    /**
     * Load all policies
     */
    @PostConstruct
    public void initStrategies() {
        strategyMap.put("one", (arg1, arg2) -> xxxStrategy.doSomethingOne(arg1, arg2));
        strategyMap.put("two", (arg1, arg2) -> xxxStrategy.doSomethingTwo(arg1, arg2));
        strategyMap.put("three", (arg1, arg2) -> xxxStrategy.doSomethingThree(arg1, arg2));
        strategyMap.put("four", (arg1, arg2) -> xxxStrategy.doSomethingFour(arg1, arg2));
        strategyMap.put("five", (arg1, arg2) -> xxxStrategy.doSomethingFive(arg1, arg2));
    }

    /**
     * Assign specific strategies to implement
     *
     * @param arg1
     * @param arg2
     * @param key Get different strategies according to the key, that is, one, two, three
     * @return
     */
    public String doSomethingByStrategy(String arg1, String arg2, String key) {
        BiFunction<String, String, String> biFunction = strategyMap.get(arg1);
        if (Objects.isNull(biFunction)) {
            // If no specific policy is found, the default implementation will be adopted (generally, this will be done only when a new type is added but the corresponding processing policy is not configured)
            log.warn("========= The processing policy is not configured, and the default implementation is adopted, key {} ====== " + key);
            return xxxStrategy.doSomethingDefault(arg1, arg2);
        }
        return biFunction.apply(arg1, arg2);
    }
}

For the specific implementation strategy, the code of xstrategyimpl class is as follows:

/**
 * All strategies are implemented in detail
 *
 * @author linzp
 * @date 2021/11/12 14:57
 */
@Component
public class XxxxStrategyImpl {

    /**
     * Default implementation
     *
     * @param arg1
     * @param arg2
     * @return
     */
    public String doSomethingDefault(arg1, arg2){
    	// Default business logic processing
        return "";
    }

    /**
     * Strategy 1
     *
     * @param arg1
     * @param arg2
     * @return
     */
    public String doSomethingOne(arg1, arg2){
    	// Business logic processing 1
        return "";
    }
    
    /**
     * Strategy 2
     *
     * @param arg1
     * @param arg2
     * @return
     */
    public String doSomethingTwo(arg1, arg2){
    	// Business logic processing 2
        return "";
    }

    /**
     * Strategy 3
     *
     * @param arg1
     * @param arg2
     * @return
     */
    public String doSomethingThree(arg1, arg2){
    	// Business logic processing 3
        return "";
    }
	
	// Omit subsequent policy codes
}

In this way, all branch judgments can be eliminated, and only two classes are used to complete the traditional strategy implementation.

summary

Good code design can make the follow-up in the face of changing needs, do not have to rush, work overtime, and leave a way for future generations to take over (dog head).

Code word is not easy ~ ~ if it helps you, point a praise PA~~

Posted by stubarny on Fri, 12 Nov 2021 02:02:41 -0800