Usage scenarios

The strategy pattern is a common behavior design pattern. To put it simply, in the system, for many types of judgment, the strategy pattern can help us avoid if… Else judgment, and the ability to dynamically select different implementations for different types, providing high scalability.

For example: there are many kinds of beauty, such as royal sister type, girl type, temperament type, so for you, for each type of sister, the method of confession must be different. Now like is royal elder sister type, for example, you can write an interface that defines a method to the royal elder sister, so you don’t just like royal elder sister in the future, also like a girl, how do you go to expand, at this time that you need to tell others, now you don’t just like royal elder sister also like a girl, need to add the if the else judge each other in the code is royal elder sister or a girl, To choose the corresponding method of confession, at this time the use of strategy mode can achieve automatic judgment of the other party is royal sister type or girl type, and automatically choose the corresponding method of confession

A policy pattern is one in which an object has a behavior, but in different scenarios, there are different algorithms for implementing that behavior, each of which is encapsulated in a separate class with a common interface, making them interchangeable. The policy pattern allows the algorithm to change without affecting the client.

The practical application

Let’s just look at a practical example. Suppose you have a requirement to listen for events in GitLab, as you can see in the following figure.

Similarly, if we are dealing with multiple policies in an interface, the policy pattern can dynamically allow an object to choose one behavior among many

  • 1. Define a policy interface
public interface GitEventStrategy  {
    /** *@paramJsonObject listens for packets *@return* /
     void handleEvent(JSONObject jsonObject) throws Exception;
}
Copy the code
  • 2. Define a policy environment class and select different implementations according to different event types in packets. BeanName is mapped to the type of the event, and the choice of policy implementation dependsjsonObject.getString("object_kind")The value of the.
@Component
public class GitEventStrategyContext {
    @Autowired
    private Map<String, GitEventStrategy> strategyMap;

    public void handle(JSONObject jsonObject) throws Exception {
        if(strategyMap ! =null) {
            GitEventStrategy eventStrategy = strategyMap.get(jsonObject.getString("object_kind"));
            if (eventStrategy == null) {
                return; } eventStrategy.handleEvent(jsonObject); }}}Copy the code
  • 3. Define specific policy implementations
@Service("merge_request")
public class GitMergeImpl implements GitEventStrategy {

    @Override
    public void handleEvent(JSONObject jsonObject) throws Exception {... }}Copy the code
  • 4. Client call
@RequestMapping("/gitEvent")
public class GitEventController {
    @Autowired
    GitEventStrategyContext gitEventStrategyContext;

    @apiOperation (httpMethod = "POST", value = "event listener ")
    @RequestMapping("handle")
    public MessageBean handle(@RequestBody JSONObject jsonObject, HttpServletRequest request) throws Exception {
        gitEventStrategyContext.handle(jsonObject);
        returnMessageBean.success(); }}Copy the code

Only the Merge_request event is implemented here. If you want to extend the implementation of other events, you just need to add an implementation class for the specific event. Is it convenient?

As you can see in the above example, different events can be switched dynamically while avoiding the use of if… Else determines the different events, and it’s very easy to extend the different events, which is the benefit of the policy pattern

structure

In policy mode, there are three roles

  • Abstract Strategy Roles: Typically implemented by an interface or abstract class. This role provides all the interfaces required by the specific policy class, as described aboveGitEventStrategyinterface
  • Context role: Holds a reference to Strategy, as aboveGitEventStrategyContext
  • ConcreteStrategy role: Encapsulates the associated algorithm or behavior, as described aboveGitMergeImpl

The use of policy patterns in ThreadPoolExecutor

Do you know what happens when the queue in the thread pool is full? The policy pattern is also used extensively in JDK source code. This chapter describes how to implement the rejection policy of ThreadPoolExecutor using the policy pattern

Abstract Policy class

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

Copy the code

Different RejectedexecutionHandlers can be selected to implement the RejectedExecutionHandler through the Constructor of ThreadPoolExecutor

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
Copy the code

When a task is added to the thread pool using the execute(Runnable) method, the core thread corePoolSize, the workQueue, and the maximum thread maximumPoolSize are all full, and the corresponding handler is used to process the rejected task

   public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null.false);
        }
        else if(! addWorker(command,false))
            reject(command);
    }
Copy the code

Executing a rejection Policy

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

Copy the code

The four different implementations are as follows:

  • Call ThreadPoolExecutor. The execute method of thread running in the rejected tasks, unless the executable program has been closed, in this case the task is discarded
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
  
        public CallerRunsPolicy(a) {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if(! e.isShutdown()) { r.run(); }}}Copy the code
  • Direct selling Java. Util. Concurrent. RejectedExecutionException anomalies
    public static class AbortPolicy implements RejectedExecutionHandler {
    
       public AbortPolicy(a) {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           throw new RejectedExecutionException("Task " + r.toString() +
                                                " rejected from "+ e.toString()); }}Copy the code
  • Discard policies. Tasks that cannot be executed are simply removed and, unlike strategy 1, no exceptions are thrown
    public static class DiscardPolicy implements RejectedExecutionHandler {
    
       public DiscardPolicy(a) {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}Copy the code
  • Tasks in the task queue header are discarded and retry
 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
   
       public DiscardOldestPolicy(a) {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if(! e.isShutdown()) { e.getQueue().poll(); e.execute(r); }}}Copy the code

So what’s the advantage of this? The RejectedExecutionHandler interface can be implemented and the RejectedExecutionHandler method can be used to extend the RejectedExecutionHandler.