Design Pattern 07-Chain of Responsibility Pattern

The chain of responsibility pattern is a behavioral design pattern, and common filter chains are designed using the chain of responsibility pattern.

1. Introduction of problems in real development scenarios

Q: Suppose there is a level-breaking game with three levels in total. Only after each level reaches the passing conditions can you enter the next level. It is implemented using java.
A: In response to this problem, according to simple ideas, we can define three categories, namely the first level, the second level, and the third level. When the client starts the game, it first enters the first level, and then enters the third level after passing the first level. Second level, and so on. We can get such code as follows:

@Slf4j
public class FirstLevel {
    
    
    @Autowired
    private SecondLevel secondLevel;
    public void play(){
    
    
        log.info("进入第一关");
        //游戏操作,获得分数
        int res = 80;
        if(res >= 80){
    
    
            secondLevel.play();
        }else {
    
    
            return;
        }
    }
}
@Component
@Slf4j
public class SecondLevel {
    
    
    @Autowired
    private ThirdLevel thirdLevel;
    public void play(){
    
    
        log.info("进入第二关");
        //游戏操作,获得分数
        int res = 90;
        if(res >= 90){
    
    
            thirdLevel.play();
        }else {
    
    
            return;
        }
    }
}
@Component
@Slf4j
public class ThirdLevel {
    
    
    public void play(){
    
    
        log.info("进入第三关");
        //游戏操作,获得分数
        int res = 95;
        if(res >= 95){
    
    
            log.info("游戏通关");
        }else {
    
    
            return;
        }
    }
}

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class DpApplicationTests {
    
    
    @Autowired
    private FirstLevel firstLevel;

    @Test
    public void client(){
    
    
        firstLevel.play();
    }
}

What's wrong with the above code?

  • Coupling is too strong, each class contains other classes
  • If you want to change the order of levels, you need to change the internal code of the class

2. Explanation of the chain of responsibility model

The application scenario of the chain of responsibility model is: a request can be processed by many processors, and these processors form a chain. The request only needs to be sent to the chain, regardless of who ultimately handles the request, or whether the request is on the chain. How was it delivered. This achieves the decoupling of the requester and the requested person. At the same time, you only need to define a chain, put the processor on the chain, and define the order of the processors. The processors do not need to hold each other.

2.1 Core classes and class diagrams

Insert image description here

There is an abstract processor interface and three concrete processors. There is a List in the processor chain class, which contains three concrete processors. The processor chain object is used in the client to interact with the request.

2.2 Basic code

  • 1 Define a processor return result type, including processing result data (type) and whether to continue to propagate on the chain
@Data
public class ProcessResult<R> {
    
    
    private boolean next;
    private R data;

    public ProcessResult(boolean next, R data) {
    
    
        this.next = next;
        this.data = data;
    }

    /**
     * 继续处理并返回数据
     * @param data
     * @param <R>
     * @return
     */
    public static <R> ProcessResult resumeData(R data){
    
    
        return new ProcessResult(true,data);
    }

    /**
     * 继续处理不返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult resumeNoData(){
    
    
        return new ProcessResult(true,null);
    }

    /**
     * 不继续处理+返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult stopData(R data){
    
    
        return new ProcessResult(false,data);
    }

    /**
     * 不继续处理+不返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult stopNoData(){
    
    
        return new ProcessResult(false,null);
    }
}

  • 2 Define an abstract processor, and the return value is the type defined above; there are two paradigms, one is the parameter received by the processor, and the other is the type of specific data returned by the processor (wrapped in ProcessResult)
public interface Processor<Result,Param> {
    
    
    /**
     * 使用范型的方法、类,需要指定范型
     * @param param 处理器参数
     * @return Res 返回结果
     */
    ProcessResult<Result> process(Param param );


    /**
     * 接口的默认方法,表示该处理器是否已经处理过请求
     * @return
     */
    default boolean isProcessed(){
    
    
        return false;
    }
}
  • 3. Define three specific processors to process requests. Use the order annotation to specify the order of JavaBean scanning. When implementing Processor, pay attention to specifying the generic parameters.
@Component
@Order(1)
@Slf4j
public class ConcreteProcessor01 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor01处理");
        //处理
        return ProcessResult.resumeData(s);
    }
}
@Component
@Order(2)
@Slf4j
public class ConcreteProcessor02 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor02处理");
        //处理
        return ProcessResult.stopData(s);
    }
}
@Component
@Order(3)
@Slf4j
public class ConcreteProcessor03 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor03处理");
        //处理
        return ProcessResult.resumeData(s);
    }
}
  • 4 Define the processor chain and inject the constructor into List

    Type parameters, the spring container will scan all P-type beans and put them in the List. Processor<Result,Param> is consistent with the ConcreteProcessor01, 02, and 03 defined earlier (i.e., Processor<String,String>). However, in order to ensure the versatility of BaseChain, multiple processor chains that aggregate different types of processors can be defined. Use generics everywhere.

@Component
@Slf4j
public class BaseChain<Result,Param> {
    
    
    private List<Processor<Result,Param>> processors;

    @Autowired
    public BaseChain(List<Processor<Result,Param>> processors) {
    
    
        this.processors = processors;
    }

    public void doProcess(Param param) {
    
    
        for (Processor processor : processors) {
    
    
            ProcessResult process = processor.process(param);
            if (process.getData() != null) {
    
    
                log.info(process.getData().toString());
            }
            if (!process.isNext()) {
    
    
                return;
            }
        }
    }
}
  • 5 clients
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class DpApplicationTests {
    
    
    @Autowired
    private BaseChain baseChain;

    @Test
    public void chainTest(){
    
    
        baseChain.doProcess("123");
    }
}

Output:
Insert image description here

3. Use the builder pattern to solve problems

The problem in 1 can be solved by applying the code in 2

4. Application examples of chain of responsibility model

HandlerInterceptor in Spring Web

The HandlerInterceptor interface is very commonly used in web development. It has three methods: preHandle(), postHandle(), and afterCompletion(). The preHandle() method is called before the request is processed, and the postHandle() method is called after the request is processed, but in the view. It is called before rendering, and the afterCompletion() method is called after the view rendering is completed.

public interface HandlerInterceptor {
    
    
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
    
    
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
    
    
	}
}

There will be many such filters. Use the chain of responsibility mode to put these filters in a chain, that is, HandlerExecutionChain, and HandlerExecutionChain will call all HandlerInterceptors.

public class HandlerExecutionChain {
    
    

	...

    //在数组中存放所有的过滤器
	@Nullable
	private HandlerInterceptor[] interceptors;
	//数组的下标
	private int interceptorIndex = -1;
	//pre的调度
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
    
    
			for (int i = 0; i < interceptors.length; i++) {
    
    
			//遍历过滤器
				HandlerInterceptor interceptor = interceptors[i];
        //preHandle返回false,进入该if,结束处理流程,
				if (!interceptor.preHandle(request, response, this.handler)) {
    
    
          //执行渲染完成后的逻辑
					triggerAfterCompletion(request, response, null);
					return false;
				}
        //修改当前指向的过滤器下标,记录最后一个成功执行preHandle()方法的拦截器的索引。
        //如果某个拦截器的preHandle()方法返回false,则遍历将停止,并且请求处理也将停止。
        //在这种情况下,HandlerExecutionChain需要调用所有已成功执行preHandle()方法的拦截器的afterCompletion()方法。
        //这时候就需要使用interceptorIndex得到最后一个成功执行的preHandle方法所在的拦截器在拦截链的位置。
				this.interceptorIndex = i;
			}
		}
		return true;
	}
}

5. Summary

5.1 Problems solved

The processor on the responsibility chain is responsible for processing the request. The client only needs to send the request to the responsibility chain, and does not need to care about the request processing details and request delivery. Therefore, the responsibility chain decouples the request sender and the request processor. .

5.2 Usage scenarios

There are many channels to filter when processing messages.

5.3 Advantages and Disadvantages

Advantages: 1. Reduce the degree of coupling. It decouples the sender and receiver of a request. 2. Simplified objects. So that the object does not need to know the structure of the chain. 3. Enhance the flexibility of assigning responsibilities to objects. Allows dynamic addition or deletion of responsibilities by changing members within the chain or moving their order. 4. It is very convenient to add a new request processing class.

Disadvantages: 1. There is no guarantee that the request will be received. 2. System performance will be affected to a certain extent, and it is inconvenient to debug the code, which may cause loop calls. 3. It may be difficult to observe runtime characteristics, which hinders debugging.


Reference:
[1] Practical combat: In-depth analysis of the chain of responsibility design pattern of design patterns
[2] Application of the chain of responsibility pattern in Spring
[3] Chain of responsibility pattern - novice tutorial

Guess you like

Origin blog.csdn.net/baidu_40120883/article/details/131642409