This is the third day of my participation in the August More text Challenge. For details, see: August More Text Challenge

0 x0, introduction

😆 Time flies, it’s Friday again, and the beauty of design patterns continues. The behavioral Pattern (62-63), the Chain of Responsibility Pattern, is often used in framework development to provide extension points for the framework, allowing the framework users to add new functions based on the extension points without modifying the framework source code. To be more specific, Interceptors and filters are most commonly used to develop frameworks.

Tips: Second-hand knowledge processing is hard to avoid mistakes, interested in time can be consulted by themselves, thank you.


0 x1, definition

The original definition

Decouple the sending and receiving of a request so that multiple receiving objects have a chance to process the request. Chain the receiving objects and pass the request along the chain until one of the receiving objects on the chain can handle it.

The definition may seem a bit abstract, but it’s simple: Build a pipeline to process a request multiple times.

Still don’t get it? It doesn’t matter, write a simple example to help understand ~


0x2. Write a simple example

If you ask your brother, baba and mama for money, the threshold is 100, 500 and 1000 in turn. You can only go down one layer at a time. For example:

The cost below 100 pieces, you can look for elder brother to solve, 100 above 500 below you have to look for father, 500 above 1000 below you have to look for ma ma ~

If -else a shuttle, it is not difficult to write code like this:

public class ChainTest {
    public static void main(String[] args) {
        Random random = new Random();
        int needMoney = random.nextInt(1500);
        System.out.println("Need:" + needMoney + "Block!");
        if(needMoney < 100) {
            System.out.println("Brother: less than 100 yuan brother still have, here you ~");
        } else {
            System.out.println("Brother: More than 100 yuan brother does not have so much money, go to find baba ~");
            if(needMoney < 500) {
                System.out.println("Baba: 500 or less, baba has it. Here you go.");
            } else {
                System.out.println("Baba: More than 500. Baba doesn't have any. Go see your mom.");
                if(needMoney < 1000) {
                    System.out.println("Ma: If less than 1000 yuan, ma can be reimbursed. Here you are ~");
                } else {
                    System.out.println("Mom: what do you need so much money for?");
                }
            }
        }
    }
}
Copy the code

The output of the code is as follows:

If there is a grandfather who loves his grandson, more than 1000 can find him for reimbursement, and an if-else must be nested. In some complex actual business scenarios, such writing method may need to set dozens of layers. For this kind of pipeline-type processing scenario, in fact, the responsibility chain mode can be used to decouple ~

You are the requester, and your family is the recipient. They will process your request in a certain order: brother → Baba → mama → grandpa. If they are within their limit, they will not go down, and if they are not, they will go down until the last recipient. This particular order can be viewed as a chain along which requests are passed

In the simplest way, each receiver holds an instance of a successor receiver, recursively calling until there is no successor receiver. The implementation code is as follows:

// Abstract handler
public abstract class AbstractHandler {
    // The next handler
    private AbstractHandler nextHandler;
    public AbstractHandler getNextHandler(a) { return nextHandler; }
    public void setNextHandler(AbstractHandler nextHandler) { this.nextHandler = nextHandler; }
    
    // Request processing
    public abstract void handleRequest(String msg, int money);
}

// Specific processing
public class BrotherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        System.out.println(msg + ":" + money + "Block!");
        if(money < 100) {
            System.out.println("Brother: less than 100 yuan brother still have, here you ~");
        } else {
            System.out.println("Brother: More than 100 yuan brother does not have so much money, go to find baba ~");
            if(getNextHandler() ! =null) getNextHandler().handleRequest(msg, money); }}}public class FatherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        if(money < 500) {
            System.out.println("Baba: 500 or less, baba has it. Here you go.");
        } else {
            System.out.println("Baba: More than 500. Baba doesn't have any. Go see your mom.");
            if(getNextHandler() ! =null) getNextHandler().handleRequest(msg, money); }}}public class MotherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        if(money < 1000) {
            System.out.println("Ma: If less than 1000 yuan, ma can be reimbursed. Here you are ~");
        } else {
            System.out.println("Mom: what do you need so much money for?");
            if(getNextHandler() ! =null) getNextHandler().handleRequest(msg, money); }}}// Test case
public class ChainTest {
    public static void main(String[] args) {
        Random random = new Random();
        int needMoney = random.nextInt(1500);
        BrotherHandler brotherHandler = new BrotherHandler();
        FatherHandler fatherHandler = new FatherHandler();
        MotherHandler motherHandler = new MotherHandler();
        // Specify the next recipient
        brotherHandler.setNextHandler(fatherHandler);
        fatherHandler.setNextHandler(motherHandler);
        // Start request delivery
        brotherHandler.handleRequest("The dog wants money.", needMoney); }}Copy the code

The output of the code is as follows:

After the chain of responsibility pattern is decoupled, it is easy to add another grandfather in four steps:

Inheritance AbstractHandler to rewrite the handleRequest () to initialize the grandpa instance, motherHandler. SetNextHandler ()

If you wanted to do the same thing with a great-grandfather or a great-great-grandfather, you might wonder if it’s a little bit overdesigned, if it’s just a simple if-else nesting, it’s a lot more classes, it’s a little bit more complicated.

The main purpose of applying design patterns is to deal with the complexity of the code, make it meet the open closed principle, and improve the extensibility of the code. It is a common way to deal with the complexity of code to break down large code logic into functions and large classes into small classes.

The chain of responsibility pattern is used here to simplify the ChainTest class by splitting the functions that handle requests into separate classes.

The client code adds a new receiver that does not need to modify the framework code, but merely extends based on the extension points provided by the framework, so to speak: the open closed principle is implemented within the framework code scope.

The usual, with UML class diagrams, component roles, usage scenarios, and a summary of the pros and cons

  • Handler → defines the interface or abstract class to handle the request, provides the method to handle the request and the method to set the next Handler;
  • ConcreteHandler → Abstracts the concrete implementation of the handler, concreting requests in chain order;

Usage scenarios

  • Runtime needs to dynamically use multiple associated objects to process the same request, such as compile, package and publish process;
  • You don’t want the user to know about specific processing logic, such as login interceptors for permission verification;
  • Process objects in process processing need to be dynamically replaced;
  • If-else Multilevel nested or verbose switch structure decoupling;

advantages

  • Reduced coupling between the client (requestor) and the object on the processing chain (receiver);
  • Dynamic combination, simplify the complexity of the object before and after the association processing, only need to store a reference to the successor;
  • Flexible extension, adding specific requestors without modifying the original system code, to meet the open closed principle;

disadvantages

  • Produces many fine-grained objects;
  • Relatively long chain of responsibility, may involve multiple processing objects, may have performance problems, and debugging is not convenient;
  • If the chain is not set up properly, it may cause circular call, lead to infinite loop, and then lead to stack overflow error.

In addition, in addition to the above recursive way to form a chain to realize the chain of responsibility pattern, you can also use data structures (arrays, lists, lists, etc.) in order to store the specific handler instances, and then traversal the way to achieve ~


0x3 “Pure and Impure” of chain of Responsibility

  • Pure chain of responsibility → either take full responsibility, or the responsibility to the next buyer, not allow to take part or all of the responsibility in one place, and then the responsibility to the next buyer;
  • Impile chain of responsibility → responsibility is partially or completely dealt with somewhere and then passed down;

0x4, Meal: Pattern application example → OkHttp interceptor core principle

Android’s famous third party request library OkHttp interceptors, we use the chain of responsibility pattern, let’s take a look at the specific implementation principle ~

â‘  Two little knowledge points

The intercept(Chain) function is overwritten by the Interceptor interface to implement a custom Interceptor. See the interface below:

Add a custom interceptor using the addInterceptor() function, following:

Look at the interceptors:

Growl, list of interceptors, add custom interceptors to this list, and see where this list is used:

Here are two points:

  • 1. Use Builder mode to separate complex object from its representation;
  • Collections. 2, Util. ImmutableList actually call unmodifiableList, is used to construct a list cannot be modified.

Cannot modify the list, the underlying principle

Inherit the List, to rewrite of modifying element function, throw UnsupportedOperationException anomalies

To verify this, write a simple code:

An error is reported after running, and the exception is as follows:

But is it really immutable? What if you change the list instead?

The print result is as follows:

Manipulating the original list causes an immutable list to change, which is easy to understand from the source code:

In proxy mode, which controls access to the target object, UnmodifiableList just wraps the target object in a layer. If it modifies the target object in some other way, it will change. So OkHttp is not directly tuned, but instead:

A new list is created based on the original list, eliminating external changes to the target object.


â‘¡ How to form a chain

Continuing with networkInterceptors, we define a public method to get a list of interceptors:

See where used, positioning to RealCall. GetResponseWithInterceptorChain () :

In addition to the list of interceptors, pay attention to an index, which is passed in with a value of 0.

Proceed (originalRequest) returns a Response object.

The core code is circled there, dismantling the next step:

  • Create a new RealInterceptorChain, still passing in a list of interceptors, but index + 1;
  • Get the current interceptor from the cursor, and call the interceptor’s intercept() method passing in the new Chain as an argument;

So let’s go back to the original interface:

The chain of responsibility pattern has two main points: how to pass the processing results later and when to finish the delivery

The first point of transmission is easy to understand:

The Interceptor implementation class calls intercept(Chain) to pass down the Chain instance, which contains the list of interceptors, the index of the next Interceptor, the processed request instance, and so on.

When will the second point end:

The last interceptor calls chain-.proceed () until it returns the Response instance, which is passed forward. Interceptors that previously called chain-.proceed () can get the Response instance and process it (such as logging). The first interceptor that calls chain-.proceed () gets the Response after it is processed.

So OkHttp’s interceptors/filters are bidirectional, and the Chain interface is the Chain that links them:

  • Request () obtain the current request instance, the interceptor can process the request, can also call the intercept to pass down the Chain;
  • Proceed () gets the current response instance. The interceptor can process the response and upload it as a return value.

How wonderful!! Whereas a normal chain of responsibility is unidirectional from beginning to end, OkHttp uses recursive return to make it bidirectional


I have to find a chance to use it. That’s all for this section. Thank you