The Chain of Responsibility Pattern creates a chain of recipient 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, typically each receiver contains a reference to another receiver. If an object cannot handle the request, then it passes the same request to the next recipient, and so on.
1Introduction to the chain of responsibility model
Intent: Avoid coupling the request sender and receiver, make it possible for multiple objects to receive requests, connect these objects into a chain, and pass the request along this chain until there is an object Until you deal with it.
Main solution: The processor on the responsibility chain is responsible for processing the request. The customer only needs to send the request to the responsibility chain. There is no need to care about the request processing details and request delivery, so the responsibility chain will The sender and request handler are decoupled.
When to use: To filter multiple channels when processing messages.
How to solve: All intercepted classes implement unified interfaces.
Key code: The Handler aggregates itself in the HandlerRequest to determine whether it is suitable. If the conditions are not met, it will be passed down. Set it before passing it to whom.
Application examples:
1. “Blowing drums and passing flowers” in Dream of Red Mansions.
2. Event bubbling in JS.
3. The processing of Encoding by Apache Tomcat in JAVA WEB, the interceptor of Struts2, and the Filter of jsp servlet.
Advantages:
1. Reduce coupling. It decouples the sender and receiver of the request.
2. Simplified objects. 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 new request processing classes.
Disadvantages:
1. There is no guarantee that the request will be accepted.
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.
2 Examples of business code usage
Use spring boot injection and interface to implement chain of responsibility
First we define an input parameter Payment
public class Payment { private boolean success; //Other parameters are omitted.... public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } }
Define another interface
public interface PaymentProcessor { /** * Node processing * * @param context */ void handle(Payment context); }
Next, we define two implementation classes CreditCardProcessor
and PayPalProcessor
. When we add a new node or implement it, we can directly implement the PaymentProcessor
interface. The spring annotation @Order
is used here to define the execution order.
@Order(1) @Component public class CreditCardProcessor implements PaymentProcessor { @Override public void handle(Payment context) { System.out.println("Processed credit card payment."); } } @Order(2) @Component public class PayPalProcessor implements PaymentProcessor { @Override public void handle(Payment context) { System.out.println("Processed PayPal payment."); } }
Finally, we also need to create a payment processing servicePaymentHandleChainService
to manage these implementation classes. ·The form of spring injection list is used here, and the order of the list is the order of the above implementation class @Order
@Service public class PaymentHandleChainService { @Autowired private List<PaymentProcessor> paymentProcessors; public void execute(Payment payment) { for (PaymentProcessor paymentProcessor : paymentProcessors) { paymentProcessor.handle(payment); } } }
The overall structure is shown in the figure below:
Let’s write a unit test:
@RunWith(SpringRunner.class) @SpringBootTest(classes = SpringExampleApplication.class) public class PaymentServiceTest { @Autowired private PaymentHandleChainService paymentHandleChainService; @Test public void test() { paymentHandleChainService.execute(new Payment()); } }
The results are shown in the figure below, which is in line with our expectations:
Abstract class implementation chain of responsibility
Another way is to define the chain through abstract classes. We still use the above example and add an abstract class here.
public abstract class AbstractPaymentProcessor { /** * next node */ protected AbstractPaymentProcessor next = null; public void execute(Payment context) throws Exception { // The upper layer was not executed successfully and will not be executed again. if (!context.isSuccess()) { return; } //Execute the current stage doHandler(context); // Determine whether there is a next responsibility chain node. If not, it means it is the last node. if (getNext() != null) { getNext().execute(context); } } public AbstractPaymentProcessor getNext() { return next; } public void setNext(AbstractPaymentProcessor next) { this.next = next; } public abstract void doHandler(Payment content) throws Exception; public static class Builder { privateAbstractPaymentProcessor head; private AbstractPaymentProcessor tail; public Builder addHandler(AbstractPaymentProcessor handler) { if (this.head == null) { this.head = handler; } else { this.tail.setNext(handler); } this.tail = handler; return this; } public AbstractPaymentProcessor build() { return this.head; } } }
Two newly defined implementation classes CreditCard2Processor
and PayPal2Processor
@Component public class CreditCard2Processor extends AbstractPaymentProcessor { @Override public void doHandler(Payment content) throws Exception { System.out.println("Processed credit card payment."); } } @Component public class PayPal2Processor extends AbstractPaymentProcessor { @Override public void doHandler(Payment content) throws Exception { System.out.println("Processed PayPal payment."); } }
This method can be used to customize nodes, which is more flexible.
@Test public void test2() throws Exception { paymentHandleChainService.execute(new Payment()); new AbstractPaymentProcessor.Builder() .addHandler(creditCard2Processor) .addHandler(payPal2Processor) .build().execute(new Payment()); }
The overall structure is as follows:
3 The difference between the responsibility chain model and the strategy model
We have talked before about how to use the strategy pattern gracefully in business code [1]. Let’s take a look at the difference between the two.
The chain of responsibility pattern and the strategy pattern are both common behavioral design patterns, but there are some differences in the problems and application scenarios they solve. The following are the main differences between the Chain of Responsibility pattern and the Strategy pattern:
1. Different problem domains:
-
Chain of Responsibility: Used to build a processing chain composed of multiple processors. Each processor attempts to process the request in turn until the request is processed or no processor in the chain can handle it. It is mainly used to separate the request sender and receiver to avoid tightly coupled processing.
-
Strategy pattern (Strategy): used to define a set of algorithms or behaviors so that they can be interchanged. It is mainly used to select different strategies according to different situations at runtime to achieve different behaviors.
3. Different focus:
-
Chain of responsibility model: focuses on the request processing flow, which connects multiple processors to form a processing chain. Each processor is responsible for processing a part of the request, or passing the request to the next processor.
-
Strategy pattern: focuses on the selection and replacement of algorithms. It encapsulates different algorithms into strategy objects, and then selects the appropriate strategy for execution as needed at runtime.
3. The calling order is different:
-
Chain of Responsibility Pattern: Requests are passed down the processing chain in turn, with each processor deciding whether to process the request or pass it on to the next processor.
-
Strategy pattern: The client code selects the appropriate strategy object and then directly calls the method of the selected strategy.
4. Different purposes:
-
Chain of responsibility model: Mainly used to handle the distribution and processing of requests, and can be used to dynamically organize and adjust the order and hierarchy of processors.
-
Strategy mode: It is mainly used to implement different algorithms or behaviors, so that the client code can choose the appropriate strategy to complete the task according to the needs.
Reference materials
[1]
How to use the strategy pattern elegantly in business code: https://juejin.cn/post/7271176998024855606
Source: juejin.cn/post/7273028474981335081
Back-end exclusive technology group
To build a high-quality technical exchange community, HR personnel engaged in programming development and technical recruitment are welcome to join the group. Everyone is also welcome to share their own company’s internal information, help each other, and make progress together!
Speak in a civilized manner, focusing on
communication technology
,recommendation for positions
, andindustry discussion
Advertisers are not allowed to enter, and do not trust private messages to prevent being deceived.