background

The Chain of Responsibility Pattern (also known as The Chain of Responsibility Pattern), as one of The common code design patterns in development design, belongs to one of The behavior patterns, which has always been familiar to our development.

The chain of responsibility mode is also one of the main design modes used by real person SDK. From obtaining relevant configuration information through the Start interface, uploading authentication materials through the Upload interface, and then submitting authentication through the Verify interface to obtain authentication results, it can be said that the logic of the whole real person business is realized in a chain way. The result of the previous business node serves as the start of the next business, thus linking the core logic of the entire SDK. Although we have seen many design patterns in the daily development process, and they are more or less applied in engineering, as the old saying goes, thinking does not necessarily mean you know, knowing does not mean you can do, doing does not mean you can say, said does not mean you can write. It is also an interesting and small challenge for developers to translate their own code and design patterns into words.

Therefore, this article aims to reorganize my impression of the design pattern, and resort to words, review the past to learn something new.

What is the chain of responsibility model

As mentioned above, the chain of responsibility pattern is a behavior design pattern that is relatively simple to understand. It allows the developer to send sequentially through the chain of processors, and each chain node has two capabilities after receiving the request:

  1. Process it (consume it)
  2. Pass it to the next handler on the chain

The chain of responsibility pattern is used when you want to give more than one object a chance to handle a request. With the chain of responsibility pattern, a chain of objects is created for a request, and each chain examines the request sequentially and either processes it or passes it on to the next object in the chain.

To understand from a certain life scenario, like a patient going to a hospital, they may traditionally go through the process from registration to doctor’s consultation, blood drawing, film taking, doctor’s follow-up visit, and finally medicine picking up at the pharmacy.

As can be seen from the life experience, the product of each node on the chain of responsibility is different (can also be the same, of course, but the same word need not through the chain of responsibility to deal with it, placed in a single object may more suitable), like chain table structure, in addition to each node needs to include to the next chain node index and when it is necessary to terminate transmission capacity, You also need to have the ability to pass products to the next node. How to abstract the products of chain nodes for different business scenarios has also become a problem in code writing. Why is it a problem? One advantage of using the chain of responsibility is that we can dynamically add or delete chain nodes to achieve the expansion of business capabilities. If we do not clearly define the output products, the related product codes will become more complex and the readability of the code will be reduced when doing chain expansion.

For example, in the real person SDK project, all process artifacts in the business chain are included in this class by creating an object, like the following code:

RealIdentityChainOutputs {
		// start process product
    public StartOutput mStartOutput;
		// upload process product
    public UploadOutput mUploadOutput;
		// Verify process product
    public VerifyOutput mVerifyOutput;
		// submit the final result
    public SubmitOutput mSubmitOutput;
    
}
Copy the code

The advantage of writing in this way is that the process products can be uniformly transmitted through a class object by passing an object. It is just like that I took a file bag containing various documents in the hospital and filled one document after each process to enter the next process, which is simple and convenient. But the problems are also clear.

First, there is the risk of code visibility. The first few chain nodes already know the data structure of the products of the next few chain nodes. Is it possible that the first few nodes have the ability to modify the products of the later nodes? Second, if there are two identical product objects in the chain passing process, it is difficult to “elegantly” create two objects of the same data according to the current product wrapper class. Should we create a List or create another object of the same class? Thirdly, each node has the ability to end the current flow process, which is also a result of the final product of chain flow. However, when put into the packaging class mentioned above, it means that a certain product is the product of the final whole chain, which is contrary to the original intention of defining the packaging class. Of course, these problems are based on the perspective of future business expansion, for real people relatively simple business scenarios, is available. Asking too many questions is “overdesigning”.

So what problem does the chain of responsibility solve?

  1. Pre-check to reduce unnecessary post-code logic
  2. Most importantly, the logic decoupling of sender and receiver(s) improves code flexibility
  3. Passing requests through the link sequence also makes the responsibility of each chain node clear, in line with the single responsibility principle
  4. This allows you to dynamically add and remove items by changing or reordering members within the chain, and also increases the flexibility of the code

Responsibility chain pattern code basic expression

Let’s look at a UML diagram of the chain of responsibility pattern.

As can be seen from the most basic UML diagram, there are generally four roles in the chain of responsibility:

  1. ClientClient, define chain rules and operation mode, dynamically generate chain according to specific business scenarios (so the chain is not fixed, can be customized combination, and select the chain head and tail)
  2. HandlerA handler interface is used to declare the general capabilities of a specific handler, typically including the ability to abstract processing and point to the next node
  3. BaseHandlerThe base handler, which is an optional role, can be placed in this class depending on the business scenario where some common logic in the specific handler can be placed
  4. ConcreteHandlersThe concrete handler constitutes the processing node in the chain. Its core function is to process the request and determine whether the request is consumed at this node or continues along the chain (the concrete handler is independent and immutable).

It can be seen that the core logic of the chain of responsibility mode is processing and transmission, and it has the ability to be flexibly customized externally.

Through the UML diagram, we can also see the fixed steps of realizing the chain of responsibility:

  1. The statementHandlerInterface Defines the interface that the node processes
  2. Eliminate duplicate template code between concrete handlers by creating an abstract handler base class
  3. Create a concrete handler subclass and its implementation method in turn to determine whether the current handler class should consume the request or continue down the chain
  4. Finally reflected to the business layer, byClientThe object itself assembles the chain nodes of the implementation, which decouples the logical processing from the calling object
The handler interface declares a method to create a handler chain. It also declares a method to execute the request.
interface Handler is
    method handle(a)method setNext(h: Handler) // The base class for simple components.abstract class BaseHandler implements Handler is

    field canHandle: boolean// If the component can handle the request, process itmethod handle(a)is
    		doCommonThings
    method setNext(h: Handler) // The original component should be able to use the default implementation of helper actionsclass ConcreteHandlerA extends BaseHandler is/ /... // Complex handlers may override the default implementationclass ConcreteHandlerB extends BaseHandler is

    method handle(a)is
        if (canHandle) // HandlerBThe treatment mode ofelse
            super.handle(a) / /... Same as above...class ConcreteHandlerC extends BaseHandler is
    field wikiPageURL: string

    method handle(a)is
        if (canHandle) // HandlerCThe treatment mode ofelse
            super.handle(a) / /Client
class Application is// Each program can configure chains in different ways.method cor(a)is
        handlerA = new ConcreteHandlerA()
        handlerB = new ConcreteHandlerB()
        handlerC = new ConcreteHandlerC()
        // ...
        handlerA.setNext(handlerB)
        handlerB.setNext(handlerC)

Copy the code

Applicable scenario

From the above description, we can also see that, in fact, as long as the logical order of processing is involved, the chain of responsibility mode can be used to deal with. However, there are two factors to consider when deciding whether to use this pattern in a real-world scenario:

  1. Is the scene complex enough, is the logic chain long
  2. Whether there are flexible business change scenario requirements

At the same time, attention should be paid to the three problems inevitably caused by the use of the chain of responsibility:

  1. Number of handlers. Traversal of request handlers in the chain, if there are too many handlers then traversal is bound to affect performance, especially in some recursive calls, so be careful
  2. When a problem occurs in the code, it is not easy to observe the characteristics of the runtime, which hampers troubleshooting
  3. Exceptions that require cover requests that are never processed even when they reach the end of the chain

The following uses an example related to the ViewGroup event passing consumption mechanism in the Android system to illustrate the way responsibility chain is used. Let’s take a look at the path that our single click on the screen takes through the Android source code.

It can be clearly seen that the Android system event transmission and distribution is also achieved through the chain. If activities, ViewGroups, and Views act as concrete handlers, consuming and passing events through their own dispatchTouchEvent() method, this is a standard chain of responsibility pattern.

// Pseudo-code logic
public boolean dispatchTouchEvent(MotionEvent ev) {

	if(onInterceptTouchEvent(ev)) {
            The onInterceptTouchEvent method is used to determine whether it needs to be consumed in this handler. If true, it is consumed in this control
            this.onTouchEvent(ev);
	} else {
            // If this control is not intercepted, it is passed to the next control's dispatchTouchEvent method
            next.dispatchTouchEvent(ev);
	}

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8fda4d05d35242edafc5d12876898ad2~tplv-k3u1fbpfcp-watermark.image?)
}
Copy the code

If the touch event has not been consumed in the View, then it will return to the original place where the call chain started according to the original link. Return to the Activity’s onTouchEvent() method from the View’s onTouch() or onTouchEvent().

As mentioned earlier in describing possible problems with the chain of responsibility pattern, a particular concern with this pattern is the possibility of code stability problems if requests are not processed at the end of the chain. Android solves this problem by redirecting the request back to the original chain node, which has the advantage of:

  1. The request is controllable no matter where it goes (that is, it is bound to be processed, even if it may end up being an empty implementation)
  2. Make uI-related feature classes behave in a consistent wayActivity,ViewGroup,ViewHave distribution and consumption capacity)

It can also be seen from the figure that the user input is taken as the starting point of the request and the request may be consumed at any node, which is a typical responsibility chain design.

Here are a few more scenarios where the chain of responsibility pattern is used in daily development, without details:

  • Login module (logical combination adjustment of various pre-account verification through responsibility chain)
  • Accounting reimbursement system (approval of next level treatment based on different authority)
  • Mail filtering system (filtering and blocking by mail attributes, such as important mail, advertising mail, spam mail, virus mail, etc.)

The relationship between the chain of responsibility model and other models

Design pattern is not a single to point to by a certain engineering design patterns exist independently in the code, but a variety of design patterns according to different business scenarios after combination, deformation, adaptation of the product of a very “rich”, and the close relationship between the responsibility chain, or what are the design patterns can cooperate with each other?

Through two roles in the chain of responsibility pattern, the sender and the receiver, as you can see it and command mode and broker mode, there is some similarity as command mode between the sender and the requestor is one-way connection is established, and the difference is that command mode more inclined to solve the parametric scenarios, such as object and a rollback operation, of course, the chain of responsibility pattern can be done in command mode.

The intermediary pattern changes the direct connection between sender and receiver to a mediation object, reducing the chaotic dependencies between objects. The relationship between design patterns is one of cooperation and transformation. There are many details, which are not the core of this article. This is also mentioned for interested readers to explore on their own.

conclusion

We use design patterns in terms of code extensibility, code stability, and code readability.

As for the chain of responsibility mode, it is a good solution to the coupling problem of the logic before and after the complex logic scenes, and it is also a solution with reference value to deal with the changeable business scenes flexibly. When we use this pattern, we need to pay special attention to the behavior of intermediate chain nodes after consumption and throw, and special scenarios where the request to reach the end of the chain is not processed.

Write in the last

Finally, I want to end with a memorable statement about how Design Patterns are used from Head First Design Patterns.

  1. Use patterns for real extensions, not just imaginary ones
  2. Simplicity is king. If you can design a simpler solution without using patterns, do it
  3. Patterns are tools, not rules, and need to be tailored to fit actual needs

reference

  • Dive-into Design Patter by Alexander Shvets
  • Head First Design Patterns Elisabeth Freeman and Kathy Sierra
  • Handle Deep Linking with Chain of Responsibility Pattern
  • Chain of Responsibility Pattern

Author: ES2049 / Dawn

The article can be reproduced at will, but please keep this link to the original text.

You are welcome to join ES2049 Studio. Please send your resume to [email protected]