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

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.

66345a3a52201ee9e1e53167ae0a3d17.jpeg

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:

7781e25b01dc6491762c3bbecddfbf7f.jpeg

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:

7aa8eb7bcdd898fcb53dec8e3ae42e74.jpeg

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:

fd82790c61bd496b800ed29b784943c7.jpeg

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, and industry discussion

Advertisers are not allowed to enter, and do not trust private messages to prevent being deceived.

30933219083c1d891455211886ee4637.png

Add me as a friend and bring you into the group