When it comes to chain of responsibility design patterns, we usually use a lot of them. For example, the gateway filter we use every day, the approval flow we use when we ask for leave, the game clearance, the log printing we use when we write code. They both use the chain of responsibility design pattern.

Let’s examine the chain of responsibility design pattern in detail

I. What is the chain of responsibility design pattern?

Official definition: The Chain of Responsibility Pattern creates a Chain of receiver objects for a request. This pattern gives the type of request and decouples the sender and receiver of the request. This type of design pattern is a behavioral pattern. In this pattern, usually each receiver contains a reference to another receiver. If an object cannot handle the request, it passes the same request to the next recipient, and so on.

In Plain English: the two bodies mentioned in the definition: the sender of the request and the receiver of the request. Take the example of employees taking time off. Sender is employees a request, the recipient is executives. “to decouple the request of the sender and the receiver”, which means that employees by decoupling. Leave the administrative examination and approval and application “created a recipient of the request object chain” : means the recipient has multiple, implements multiple recipients for examination and approval of the chain.

Ii. The use scenarios of the chain of responsibility design pattern

  • Gateway filter: When a URL request comes, first check whether the URL is valid. If the url is invalid, filter it out and enter the next level of authentication. Whether the device is in the blacklist. If the device is filtered out, the next-layer check is not performed. Verify parameter compliance, non-compliance filtering out, compliance into the next level of verification, and so on.
  • Leave approval flow: The number of days for leave is less than 3 days, which can be approved by the direct leadership; More than 3 days, less than 10 days, the approval of the department head; More than 10 days should be approved by the general manager
  • Finish the game: To enter the second level, complete the first level with a score >90; Complete the second level, score >80, to enter the third level, etc
  • Log processing: Log levels are dubug, INFO, WARN, and ERROR from small to large.
    • Console: The console receives debug level logs. All debug, INFO, WARN, and error logs are printed on the Console.
    • File file: file Receives info logs. In this case, info, WARN, and ERROR logs are printed in the file, but debug logs are not printed
    • Error files: Only logs of the error level are received. Logs of other categories are not received.

Iii. The realization of the responsibility chain design pattern

The following is a simple case [leave approval flow] to illustrate the implementation of the chain of responsibility

1. Requirements:

I have an employee, Xiao Li, who wants to request. Company regulations, leave within 3 days, direct leadership can be approved. A leave of 3-10 days requires the approval of the department manager. Leave more than 10 days should be approved by the general manager.

2. Common implementation

For this approval stream, our first thought was to use if…. else…. To write about.

public void approve(Integer days) {
    if (days <= 3) {
        // Direct leadership approval
    } else if (days > 3 && days <= 10) {
        // Department manager approval
    } else if (days > 10) {
        // General Manager approval}}Copy the code

And it does work that way. But he has several disadvantages:

  1. This approval method is long, and a large chunk of code doesn’t look pretty. It looks like very little code here, but that’s because I didn’t actually implement the approval logic. When there are a lot of approvers, if… else… It’s going to be a lot, it’s going to look bloated.
  2. Poor scalability: Join now has a home approval stream between the department manager and the general manager. If we change the original code, we may introduce bugs and violate the open-close principle.
  3. Violation of the single responsibility principle: This class takes on multiple responsibilities for multiple roles, violating the single responsibility principle.
  4. No cross-level approval: there is a special person to join, he asked for leave for 3 days, which also needs the approval of the general manager, this if… else…. It’s not going to happen.

Since it is possible to add multiple approvers, we can consider subclassing specific approvers as approvers, using polymorphism.

3. Realization mode of chain of responsibility

Step 1: Small force leave, define a leave entity class LeaveRequest. This is the sender of the request

@Data
public class LeaveRequest {
    /** * Ask for leave */
    private String name;

    /** * Days of leave */
    private int days;

    public LeaveRequest(a) {}public LeaveRequest(String name, int days) {
        this.name = name;
        this.days = days; }}Copy the code

There are two attributes, name, and days.

The second step: abstract leave approval

/** * Abstract leave processing class */
@Data
public abstract class LeaveHandler {
    /**
     * 处理人姓名
     */
    private String handlerName;

    /**
     * 下一个处理人
     */
    private LeaveHandler nextHandler;

    public void setNextHandler(LeaveHandler leaveHandler) {
        this.nextHandler = leaveHandler;
    }

    public LeaveHandler(String handlerName) {
        this.handlerName = handlerName;
    }

    /** * The specific processing operation *@param leaveRequest
     * @return* /
    public abstract boolean process(LeaveRequest leaveRequest);
}
Copy the code

The following is defined here:

  1. Name of approver,
  2. The process() method that the approver performs. The approval content is the leave information, and the return value is the approval result, pass or fail
  3. NextHandler nextHandler: this is the point. It is also the key to continuous execution of our chain.

Step 3: Define the specific operator

  • Direct handler class: DirectLeaveHandler.java
/** * Days less than 3 days, the direct leadership to handle */
public class DirectLeaveHandler extends LeaveHandler{
    public DirectLeaveHandler(String directName) {
        super(directName);
    }
    @Override
    public boolean process(LeaveRequest leaveRequest) {
        // If the random number is greater than 3, the parameter is approved. Otherwise, the parameter is not approved
        boolean result = (new Random().nextInt(10)) > 3;
        if(! result) { System.out.println(this.getHandlerName() + "Approval rejected.");
            return false;
        } else if (leaveRequest.getDays() <= 3) {
            // The approval is approved
            System.out.println(this.getHandlerName() + "Approval completed");
            return true;
        } else{
            System.out.println(this.getHandlerName() + "Approval completed");
            return this.getNextHandler().process(leaveRequest); }}}Copy the code

The process of leadership approval is simulated here. If the period is less than 3 days, the direct leadership will directly approve, may pass, may not pass. If more than 3 days, submit to the next level for approval.

  • Department manager handling class: ManagerLeaveHandler
public class ManagerLeaveHandler extends LeaveHandler{

    public ManagerLeaveHandler(String name) {
        super(name);
    }
    @Override
    public boolean process(LeaveRequest leaveRequest) {
        // If the random number is greater than 3, the parameter is approved. Otherwise, the parameter is not approved
        boolean result = (new Random().nextInt(10)) > 3;
        if(! result) { System.out.println(this.getHandlerName() + "Approval rejected.");
            return false;
        } else if (leaveRequest.getDays() > 3 && leaveRequest.getDays() <= 10) {
            System.out.println(this.getHandlerName() + "Approval completed");
            return true;
        } else {
            System.out.println(this.getHandlerName() + "Approval completed");
            return this.getNextHandler().process(leaveRequest); }}}Copy the code

The department manager deals with 3-10 days of vacation, if more than 10 days, it will be submitted to the next level of leadership for approval.

public class GeneralManagerLeavHandler extends LeaveHandler{
    public GeneralManagerLeavHandler(String name) {
        super(name);
    }
    @Override
    public boolean process(LeaveRequest leaveRequest) {
        // If the random number is greater than 3, the parameter is approved. Otherwise, the parameter is not approved
        boolean result = (new Random().nextInt(10)) > 3;
        if(! result) { System.out.println(this.getHandlerName() + "Approval rejected.");
            return false;
        } else {
            System.out.println(this.getHandlerName() + "Approval completed");
            return true; }}}Copy the code

The left and right holidays that eventually flow to the general manager will be approved

Step 4: Define the client to initiate the request operation

    public static void main(String[] args) {
        DirectLeaveHandler directLeaveHandler = new DirectLeaveHandler("Direct Supervisor");
        ManagerLeaveHandler managerLeaveHandler = new ManagerLeaveHandler("Department Manager");
        GeneralManagerLeavHandler generalManagerLeavHandler = new GeneralManagerLeavHandler("General Manager");

        directLeaveHandler.setNextHandler(managerLeaveHandler);
        managerLeaveHandler.setNextHandler(generalManagerLeavHandler);

        System.out.println("======== Zhang SAN asks for 2 days ==========");
        LeaveRequest lxl = new LeaveRequest("Zhang".2);
        directLeaveHandler.process(lxl);
        
        System.out.println("======== Lee Si asks for a 6-day leave ==========");
        LeaveRequest wangxiao = new LeaveRequest("Bill".6);
        directLeaveHandler.process(wangxiao);


        System.out.println("======== Wang Wu asks for a 30-day leave ==========");
        LeaveRequest yongMing = new LeaveRequest("Fifty".30);
        directLeaveHandler.process(yongMing);
    }
Copy the code

Here we created a direct leader, a department manager, and a general manager. And set the relationship between the superior and the subordinate. Then judge how to approve according to the number of days the employee asks for leave. For the user, he does not need to know how many leaders need to be approved. He just needs to submit it to the first leader, who is the direct leader, and then go on and on for approval. That is, in the chain of responsibility design pattern, we only need to get to the first handler on the chain, and then every handler on the chain has a chance to process the corresponding request.

The above code basically summarizes the use of the chain of responsibility design pattern, but the above client code is actually quite cumbersome. We will continue to optimize the chain of responsibility design pattern later.

Step 5: Look at the results

Because the leave is random, it may be rejected. Let’s take a look at the result of an all-yes request first

======== Zhang SAN asks for leave2Days ========== direct supervisor approval completed ======== Li Si leave6Day ========== direct supervisor approval completed department manager approval completed ======== Wang Wu leave30Day ========== Direct supervisor approval completed Department manager approval completed general manager approval completedCopy the code

Let’s look at the results of the rejected requests

======== Zhang SAN asks for leave2Day ========== direct supervisor approval reject ======== Li 4 leave6Days ========== direct supervisor approval rejected ======== Wang Wu leave30Day ========== Direct supervisor approval completed department manager approval rejectedCopy the code

4. Abstract summary of chain of responsibility concept

Chain of responsibility design pattern: A client makes a request, and all the objects on the chain have a chance to process the request without the client knowing who the specific handler is. Multiple objects have the opportunity to process the request, thereby avoiding coupling between the sender and receiver of the request. Chain the objects and pass the request along the chain until an object processes it

The above code basically summarizes the use of the chain of responsibility design pattern, but the above client code is actually quite cumbersome. I will optimize the chain of responsibility design pattern later.

4. Advantages and disadvantages of the chain of responsibility design pattern

advantages

Dynamic combination to decouple requestor and receiver. The requester and receiver are loosely coupled: the requester does not need to know the receiver, nor does it need to know how to handle it. Each person is responsible for his or her own area of responsibility, leaving the rest to his or her successors. The components are fully decoupled. Dynamically combining responsibilities: The chain of responsibilities pattern divides functions into individual responsibility objects and then dynamically combines them into chains when used, allowing for flexible assignment of responsibility objects and flexible addition and change of object responsibilities.

disadvantages

Many fine-grained objects are generated: because functional processing is dispersed into separate responsibility objects, each object has a single function, many responsibility objects are needed to complete the entire process, resulting in a large number of fine-grained responsibility objects. Not necessarily: Each responsibility object is responsible for its own part, so a request can come up and no responsibility object can process it, even if it runs the entire chain. This requires providing default processing and paying attention to the validity of the construct chain.

Comprehensive case — Gateway permission control

1. Identify your needs

The gateway has many functions: API traffic limiting, blacklist blocking, permission authentication, parameter filtering, and so on. Let’s implement gateway access control through the chain of responsibility design pattern.

2. Implementation ideas

Take a look at the class diagram below.

You can see that an abstract gateway handler is defined. Then there are implementation classes for the four subprocessors.

3. Concrete implementation

Step 1: Define an abstract gateway handler class

/** * Defines an abstract gateway processor class */
public abstract class AbstractGatewayHandler {
    /** * Defines the next gateway processor */
    protected AbstractGatewayHandler nextGatewayHandler;

    public void setNextGatewayHandler(AbstractGatewayHandler nextGatewayHandler) {
        this.nextGatewayHandler = nextGatewayHandler;
    }

    /** * Abstracts the services performed by the gateway *@param url
     */
    public abstract void service(String url);
}
Copy the code

Step 2: Define specific gateway services

1. API interface flow limiting processor

/** * API stream limiting processor */
public class APILimitGatewayHandler extends AbstractGatewayHandler {
    @Override
    public void service(String url) {
        System.out.println("API interface flow limiting processing, processing completed");
        // Implement a specific flow limiting service process
        if (this.nextGatewayHandler ! =null) {
            this.nextGatewayHandler.service(url); }}}Copy the code

2. Blacklist the interceptor processor

/** * Blacklist processor */
public class BlankListGatewayHandler extends AbstractGatewayHandler {
    @Override
    public void service(String url) {
        System.out.println("Blacklist processing, processing completed.");

        // Implement a specific flow limiting service process
        if (this.nextGatewayHandler ! =null) {
            this.nextGatewayHandler.service(url); }}}Copy the code

3. Permission verification processor

/** * Permission validation processor */
public class PermissionValidationGatewayHandler extends AbstractGatewayHandler {
    @Override
    public void service(String url) {
        System.out.println("Permission verification processing, processing completed");
        // Implement a specific flow limiting service process
        if (this.nextGatewayHandler ! =null) {
            this.nextGatewayHandler.service(url); }}}Copy the code

4. Verify the processor

/** * Parameter validation processor */
public class ParameterVerificationGatewayHandler extends AbstractGatewayHandler {
    @Override
    public void service(String url) {
        System.out.println("Parameter verification processing, processing completed");
        // Implement a specific flow limiting service process
        if (this.nextGatewayHandler ! =null) {
            this.nextGatewayHandler.service(url); }}}Copy the code

Step 3: Define the gateway client and set up the gateway request chain

/** * Gateway client */
public class GatewayClient {
    public static void main(String[] args) {
        APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
        BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
        ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
        PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();

        apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
        blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
        parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);
        
        apiLimitGatewayHandler.service("http://www.baidu.com"); }}Copy the code

So, just like before, without too much explanation, here’s how it works:

API interface traffic limiting processing, processing complete blacklist processing, processing complete parameter verification, processing complete permission verification processing, processing completeCopy the code

This does a series of gateway processing. Of course, each processing should return the result and then decide whether to proceed with the next processing. So this simplifies things

Step 4: Optimize the chain of responsibility design pattern using the factory pattern

In the third step on the gateway client, the chain of responsibility is initialized. This way, every time the client wants to make a request, it has to perform an initialization operation, which is not necessary. We can use the factory design pattern to extract the client into the factory, taking the first handler on the chain at a time.

1. Define the gateway processor factory

/** * Gateway processor factory */
public class GatewayHandlerFactory {
    public static AbstractGatewayHandler getFirstGatewayHandler(a) {
        APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
        BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
        ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
        PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();

        apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
        blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
        parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);

        returnapiLimitGatewayHandler; }}Copy the code

The gateway processor factory defines the relationship between the various gateway processors and returns the first gateway processor.

2. Optimize the gateway client

/** * Gateway client */
public class GatewayClient {
    public static void main(String[] args) {
        GatewayHandlerFactory.getFirstGatewayHandler().service("http://www.baidu.com"); }}Copy the code

We just need to call the first gateway processor directly on the client side, and we don’t need to care about other processors.

V. Summary of the chain of responsibility model

  1. Define an abstract superclass in which the method of request processing and the next handler are defined.
  2. The subclass processor then inherits from the classification processor and implements its own request handling methods
  3. To set up the chain of processing requests, you can use the factory design pattern abstraction so that the requester only needs to know the first link of the chain