Everyone should be able to understand the design pattern - strategy pattern

Posted by rubberjohn on Mon, 03 Jan 2022 16:31:56 +0100

preface

Policy pattern is one of the most commonly used design patterns, especially when eliminating if else statements. It transforms a set of behaviors into objects that can be replaced with each other within the original context object

problem

One day, you create a tour guide program for tourists. The first version of the program can support planning driving routes, and people who travel by car are very satisfied with it. With the popularity of the program, you have added the function of walking planning to the program in the next version. At this time, there are only two routes for the program. The program is very simple to write. However, in the next version, the function of riding planning is added, and then the bus route planning is added. After a period of time, you have to plan the route for browsing all scenic spots in the city.

From a business perspective, your program has become very successful as its popularity increases. However, from a technical point of view, each time a new route planning algorithm is added, the volume of the main classes in the program will double.

Finally, at some point in the future, you won't be able to maintain this program. Whether it is a simple BUG repair or fine-tuning the weight of a street in the algorithm, any modification to an algorithm will affect the whole class. This increases the risk of introducing errors into otherwise normal code. Teamwork also becomes inefficient, and no one is willing to maintain a lengthy piece of code. Even new members of the team complain that it takes too much time to merge code conflicts.

Solution

In the policy pattern, it is suggested to find out the classes responsible for completing specific tasks in different ways, and then extract the algorithms from them into a separate class called policy.

The original class named context contains a member variable to store references to each policy. The original class does not execute the task, but delegates the task to the connected policy object.

The context is not responsible for selecting which algorithm meets the needs of the task - the client will pass the required policy to the context. In fact, the context is indifferent to policy. Just interact with all policies through the same common interface. This common interface only needs to expose a method to trigger the method encapsulated in the selected policy.

Therefore, context is independent of policy. In this way, new algorithms can be added or existing algorithms can be modified without modifying the context code or other strategies.

For the policy pattern structure in the figure below

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. 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.
  5. 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.

Implementation steps

  1. Find the algorithm with high modification frequency from the context class (original 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 to implement the general policy interface.
  4. Add a member variable in the context class to hold the reference to the policy object. Provides a setter to be able to modify the member variable (Policy). The top and bottom only interact with the policy object through the policy interface.
  5. The client associates the context with the corresponding policy so that the context can complete the work as expected.

Applicable scenario

When there are many similar classes that are only slightly different when performing certain behaviors

If the algorithm is not particularly important in the logic of the context, you want to isolate the context from the implementation details of the algorithm.

When complex conditional operators are used in the class to switch between different variants in the same algorithm.

Examples of policy patterns in the core Java library

  1. java.util.Comparator#compare() Calls for are from collisions #sort()
  2. javax.servlet.http.HttpServlet#service() method
  3. javax.servlet.Filter#doFilter()

Actual combat scene

The policy model is especially suitable for implementing various payment methods in e-commerce applications. Customers need to select a payment method in a payment scenario: wechat payment or AliPay.

At the same time, we take the mail sending of different mail service providers as an example:

In order to eliminate if else statements, we can do so in Spring applications

public interface MailStrategyService {
​
    /**
     * Service provider name
     *
     * @return
     */
    public String getServiceProviderName();
​
    /**
     * Send mail
     *
     * @param message
     */
    public void send(String message);
​
}
@Service
@Slf4j
public class QQMailStrategyServiceImpl implements MailStrategyService {
​
    @Override
    public String getServiceProviderName() {
        return "qq";
    }
​
    @Override
    public void send(String message) {
       log.info("towards QQ Send mail to mailbox:{}",message);
    }
}

Core areas to learn:

@Component
@Slf4j
@Getter
public class MailStrategyContext {
​
    /**
     * strategy
     * KEY Code for business
     * VALUE Class for concrete implementation
     */
    private final ConcurrentHashMap<String, MailStrategyService> strategy = new ConcurrentHashMap<>();
​
    /**
     * Inject all classes that implement the MailStrategyService interface
     * The method of constructing injection is used here to inject beans
     *
     * @param mailServiceList
     */
    public MailStrategyContext(List<MailStrategyService> mailServiceList) {
        log.info("Start injection strategy");
        mailServiceList.forEach(mailServiceImpl -> {
            log.info("Current policy class:{}", mailServiceImpl.getClass().getName());
            strategy.put(mailServiceImpl.getServiceProviderName(), mailServiceImpl);
        });
        log.info("Injection strategy completed");
    }
​
    /**
     * Send mail
     *
     * @param strategyName Policy name
     * @param message      Send mail message
     */
    public void send(String strategyName, String message) {
        MailStrategyService mailStrategyService = strategy.get(strategyName);
        mailStrategyService.send(message);
    }
}
@RestController
@RequestMapping("/mail")
public class StrategyController {
    @Autowired
    private MailStrategyContext mailStrategy;
​
    @PostMapping("/{bussinessCode}/send")
    public void sendMail(@PathVariable(value = "bussinessCode") String bussinessCode,
                         @RequestParam(value = "message") String message) {
        mailStrategy.send(bussinessCode, message);
    }
}

We strongly recommend an online design pattern learning website: https://refactoringguru.cn/design-patterns/

Topics: Java