How to use the chain of responsibility pattern elegantly in business code

8108c64b413db734e51d8e1135db9feb.gif

By using the chain of responsibility model, we can handle requests more flexibly and gracefully, reduce the coupling between codes, and improve the maintainability and scalability of the code. In some scenarios with complex business logic or where requests need to be processed dynamically, using the chain of responsibility pattern will be a good choice. This article will use a specific example to introduce in detail how to use the chain of responsibility pattern elegantly in business code.

0b5d2a75e9a77a9c12dff4eafecd1e85.png

introduction

In software development, we often encounter a situation where a request needs to go through multiple processing nodes to get the final result. In this case, we can use the chain of responsibility pattern to handle the request gracefully. The Chain of Responsibility pattern is a behavioral design pattern that creates a chain of multiple processing nodes, each of which has the opportunity to process a request or decide to pass the request to the next node. This pattern decouples the sender and receiver of requests, thereby improving code flexibility and maintainability.

Usage scenarios of the chain of responsibility model usually include the following situations:

  1. Multiple objects can handle the request, but which object handles the request is determined at runtime.

  2. Want to submit a request to one of multiple objects without explicitly specifying the recipient.

  3. You want to dynamically specify a collection of objects that handle requests.

4df5a16f823a8df64c4ff72a64fdaf2e.png

Definition of chain of responsibility model

? Definition

Give multiple objects a chance to handle the request, thus avoiding coupling the sender of the request to the request handler. These objects are connected into a chain, and the request is passed along this chain until an object handles it.

? Type

object behavior pattern

? Substance

The processor in the chain of responsibility is responsible for processing the request. The client only needs to send the request to the chain of responsibility and does not need to care about the details of request processing and the delivery of the request, thus realizing the decoupling of the request sender and the request processor.

a3804dd6d6d487b63b94b36dbcf53d8f.png

elegance

The main benefits and goals of using the chain of responsibility model are as follows:

  1. Decoupling the nodes of the chain of responsibility: The chain of responsibility pattern can decouple the sender and receiver of the request so that they do not need to directly reference each other, thereby reducing the coupling between objects.

  2. Improve code flexibility and maintainability: The chain of responsibility model can dynamically change the order and number of processing nodes at runtime, thereby flexibly processing different requests, and also facilitates code maintenance and expansion.

  3. It is convenient to add new processing nodes: Since the responsibility chain model decouples the processing nodes, it is easy to add new processing nodes to the responsibility chain without modifying the existing code.

  4. Dynamically handle requests: The chain of responsibility pattern allows handlers to be dynamically combined and ordered based on specific situations at runtime, enabling flexible request handling logic.

By using the chain of responsibility model, we can handle requests more flexibly and gracefully, reduce the coupling between codes, and improve the maintainability and scalability of the code. In some scenarios with complex business logic or where requests need to be processed dynamically, using the chain of responsibility pattern will be a good choice. Next, we will use a specific example to introduce in detail how to use the chain of responsibility pattern elegantly in business code.

6cb559a82b59323878ab9d7d4772e45a.png

The chain of responsibility model is suitable for application scenarios

When the program needs to handle different types of requests in different ways, and the request type and order are not known in advance , You can use the chain of responsibility model .

This pattern can connect multiple processors into a chain. After receiving the request, it “asks” each handler if it can handle it. This way all handlers have a chance to handle the request.

This mode can be used when multiple handlers must be executed in sequence.

Regardless of the order in which you connect handlers into a chain, all requests will pass through the handlers in the chain in strict order.

If the required processors and their order must change at runtime, the Chain of Responsibility pattern can be used.

If you have set methods for reference member variables in the handler class, you will be able to dynamically insert and remove handlers, or change their order.

? Implementation method
  1. Declare the handler interface and describe the signature of the request handling method.

    Determine how the client passes request data to the method. The most flexible way is to convert the request into an object and pass it as a parameter to the handler function.

  2. To eliminate duplicate sample code in concrete handlers, you can create an abstract handler base class based on the handler interface.

    This class needs to have a member variable to store a reference to the next handler in the chain. You can make it an immutable class. But if you plan to make changes to the chain at runtime, you need to define a setter method to modify the value of the reference member variable.

    For ease of use, you can also implement the default behavior of the processing method. If there are any objects left, this method passes the request to the next object. Concrete handlers can also use this behavior by calling methods on the parent object.

  3. Create specific handler subclasses and implement their processing methods in turn. Each handler must make two decisions after receiving a request:

  • Whether to handle this request yourself.

  • Whether to pass the request along the chain.

Clients can assemble the chain themselves or obtain preassembled chains from other objects. In the latter case, you must implement a factory class to create the chain based on configuration or environment settings.

The client can trigger any handler in the chain, not just the first one. The request will be passed through the chain until a handler refuses to pass further, or the request reaches the end of the chain.

Due to the dynamic nature of the chain, clients need to be prepared to handle the following situations:

    • There may be only a single link in the chain.

    • Some requests may not reach the end of the chain.

    • Other requests may not be processed until the end of the chain.

1f8be445c86ea9253078ed145ecf9e71.png

Application examples

Let’s take the scenario of asking for leave to illustrate. The time we ask for leave is divided into 3 days, 5 days, 7 days, etc. The number of leave days will be approved by different people. Maybe a 3-day leave will be approved by your project team leader, but a 5-day leave may require approval. Approved by the department head. In this scenario, it is very suitable to use the responsibility chain model for construction. Of course, you can also use a large number of if-else to handle it, but compared to if-else, the responsibility chain model has several advantages:

  1. Compared with if…else, the chain of responsibility pattern is less coupled because it distributes conditional judgments into various processing classes, and the priority processing order of these processing classes can be set arbitrarily, and if you want Adding a new handler class is also very simple, which conforms to the open-closed principle.

  2. The chain of responsibility model brings flexibility, but when setting up the context of processing classes, you must avoid the problem of circular references in the chain.

40dd4ccd0d6ae75bdc9122ba36d6b198.png

6bfc5ae67c539b525e7035706e91a7cb.png

Implementation steps

? Steps of Circular Chain of Responsibility Model

Using a for loop to handle the chain of responsibility can explain the execution process of the chain of responsibility pattern more intuitively.

Suppose we have a chain of responsibility that includes multiple processors Processor1, Processor2, and Processor3. Each processor can handle the request. If a processor can handle the request, it processes and ends the execution of the chain of responsibility; if the processor cannot handle the request, the request is passed to the next processor.

We can use a for loop to iterate through the processors in the chain of responsibility and process them in sequence. When the first processor that can handle the request is encountered, the request is processed and the loop ends.

Request request = new Request(); // Create request object


Processor[] processors = {new Processor1(), new Processor2(), new Processor3()};


for (Processor processor : processors) {
    if (processor.canHandle(request)) {
        processor.process(request);
        break; // Process the request and end the loop
    }
}

In the above code, we traverse the processors in the responsibility chain in turn and determine whether the request can be processed by calling the canHandle method. If it can be processed, call the process method to process it, and use the break statement to end the loop. If all processors cannot handle the request, the corresponding processing logic can be performed after the loop ends.

Using a for loop to handle the chain of responsibility can express the execution process of the chain of responsibility pattern more clearly. We traverse the processors in order and process them as needed. If multiple processors can handle the request, we can prioritize the processing by adjusting the order of the processors. At the same time, we can also perform corresponding processing according to actual needs after the loop ends, such as reporting an error or giving default processing, etc.

996c05be0210ed7deae3f540b2bda455.png

The design idea of the chain of responsibility pattern is to pass a request along a chain, and each node has the opportunity to process the request or pass it to the next node. This design is similar to the workflow on an assembly line. Each node is responsible for processing the work of a certain link and passing the results to the next node. The execution flow of the Chain of Responsibility pattern can be well represented using a flowchart. Each node can be represented as a step in a flow chart, with arrows indicating the direction of the request. The entire flow chart can clearly show the work sequence and process of each node in the chain of responsibility model.

Through the abstract representation of the flow chart, we can better understand the execution process of the chain of responsibility model, facilitate communication and communication, and also help design and implement the chain of responsibility model. At the same time, flow charts can also help developers better understand and maintain the code logic of the chain of responsibility model.

? Leave Scenario Steps

The chain of responsibility model in the leave scenario can be implemented by implementing a LeaveRequestProcessor interface. Each specific processor implements this interface and determines whether the request can be processed based on the number of days of leave and whether to pass the request to the next processor.

Using a for loop to implement the chain of responsibility pattern can more intuitively display the execution process of the chain of responsibility:

First, define the LeaveRequestProcessor interface:

public interface LeaveRequestProcessor {
    void processLeaveRequest(LeaveRequest request);
}

TeamLeaderProcessor (project team leader processor):

public class TeamLeaderProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() <= 3) {
            // Handle leave request
            System.out.println("The project team leader approved the leave application, the number of days is:" + request.getDays() + "days");
        } else {
            System.out.println("The project team leader cannot process the leave application");
        }
    }
}

DepartmentManagerProcessor (department manager processor):

public class DepartmentManagerProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() > 3 & amp; & amp; request.getDays() <= 5) {
            // Handle leave request
            System.out.println("The department manager approved the leave application, the number of days is:" + request.getDays() + "days");
        } else {
            System.out.println("The department manager cannot process the leave application");
        }
    }
}

CEOProcessor (general manager processor):

public class CEOProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() > 5 & amp; & amp; request.getDays() <= 7) {
            // Handle leave request
            System.out.println("The general manager approved the leave application, the number of days is:" + request.getDays() + "days");
        } else {
            System.out.println("The general manager cannot process the leave application");
        }
    }
}

Then, create a list of processors and use a for loop to iterate through the list of processors to handle leave requests:

List<LeaveRequestProcessor> processors = new ArrayList<>();
processors.add(new TeamLeaderProcessor());
processors.add(new DepartmentManagerProcessor());
processors.add(new CEOProcessor());


//Create a leave request
LeaveRequest request = new LeaveRequest("John", 5);


for (LeaveRequestProcessor processor : processors) {
    processor.processLeaveRequest(request);
}

In the above code, we create a processor list and add three specific processors: project team leader (TeamLeaderProcessor), department manager (DepartmentManagerProcessor) and general manager (CEOProcessor). We then use a for loop to iterate through the list of processors and call each processor’s processLeaveRequest method in turn to handle the leave request.

When each processor processes a request, it determines whether it can handle the request based on the number of days of leave. If the request can be processed, proceed and end the loop; if the request cannot be processed, proceed to the next processor. In this way, the request is passed through the list of processors until a processor is found that can handle the request.

By using the for loop to implement the chain of responsibility pattern, we express the execution process of the chain of responsibility pattern more intuitively. At the same time, by adjusting the order of processors in the processor list, the priority of processing requests can be flexibly changed. When the business logic changes, we only need to add or modify the corresponding processor, without modifying the existing processor or request sender code, which improves the maintainability and scalability of the code.

In this way, through the chain of responsibility model, we can flexibly handle leave requests and determine which processor will approve them based on the number of days of leave. When the business logic changes, we only need to add or modify the corresponding processor, without changing the code of the existing processor or request sender, which improves the maintainability and scalability of the code.

8f040bf28fb1415bf70d88db7f5e29f8.png

References

  1. https://juejin.cn/post/7273028474981335081Usage of chain of responsibility

  2. https://refactoringguru.cn/design-patterns/chain-of-responsibility chain of responsibility pattern

549bb5c1b1acf5b7455ed2ffc9e089fb.png

team introduction

Taotian Group – content consumption & community interaction team is a star team of Alibaba. It is currently responsible for the homepage and information flow recommendation of Alibaba e-commerce platform, including mobile Taobao homepage, shopping, information flow, post-purchase links, etc. The scenario serves hundreds of millions of users every day, and the peak QPS of the core system during the big promotion is tens of millions. The work involves end-to-end performance optimization of the entire link, traffic efficiency improvement, user experience, increasing the enthusiasm of merchants and experts to participate in Taobao, and optimizing the business ecological operating mechanism. . In the past few years, I have been working closely with the industry’s leading algorithm team to continuously expand business boundaries and step on core business indicators time and time again. We focus on mobile Taobao homepage, Taobao shopping, recommended information flow core link business support and business platform abstraction. There is huge traffic here, which can satisfy your imagination of practicing high-concurrency large-scale distributed systems; here are the most cutting-edge algorithm application scenarios, where you can play with various intelligent innovations; here are the most stringent system index requirements, which can make you You feel the thrill of optimizing complex systems~

We are currently recruiting interested students (both school and social recruitment). Welcome to communicate or submit your resume to [email protected].

¤ Extended reading ¤

3DXR technology | Terminal technology | Audio and video technology

Server technology | Technical quality | Data algorithm