1

background

The Chain of Responsibility Pattern (also known as The Chain of Responsibility Pattern), as one of the commonly used code design patterns in development and design, is one of the behavioral patterns and has always been familiar to us in development.

One of the main chain of responsibility pattern is a real person SDK design patterns used by the start access to relevant configuration information interface to upload interface to upload material certification, follow-through verify get certified interface to submit certified results, we can say the whole logic of real people business Realized in a chain, the result of the previous business node is used as the start of the next business, thus stringing up the core logic of the entire SDK. Although we have seen a lot of design patterns in the daily development process, they are also more or less applied in engineering, but as the old saying goes, we don’t know when we think about it, we don’t know we can do it, and it doesn’t mean we can say it. Come out, what is said may not be able to be written. How to translate the code that I have written and the design patterns used into text is also a very interesting thing and a small challenge for developers.

Therefore, this article aims to re-organize the design patterns in my impression, and resort to words to learn the new by reviewing the past.

What is the chain of responsibility model

As mentioned above, the chain of responsibility model is a behavioral design model that is relatively simple to understand. It allows developers to send sequentially through the processor chain. After receiving the request, each chain node has two capabilities:

  1. Process it (consumption)
  2. Pass it to the next processor on the chain

When you want more than one object to have a chance to handle a request, you can use the chain of responsibility model. Through the chain of responsibility model, an object chain is created for a request. Each object chain checks the request in sequence and processes it, or passes it to the next object in the chain.

image.png

Understanding from a certain life scenario, just like a patient going to a hospital to see a doctor, traditionally they may experience the process from registration to doctor's consultation, blood drawing and filming, to doctor's follow-up visit, and finally to the pharmacy to get the medicine.

image.png

It can be seen from life experience that the product of each node in the responsibility chain is different (of course it can also be the same, but if the same is not necessary to deal with the responsibility chain, it may be more appropriate to put it in a single object), like The structure of the linked list is the same. In addition to the index to the next chain node and the ability to terminate the transmission if necessary, each node also needs to have the ability to deliver the product 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? Because the big advantage of using the responsibility chain is that we can dynamically add or delete chain nodes to achieve the expansion of business capabilities. If our definition of the output product is not clear enough, it will lead to the related expansion of the chain. The product code will become more complicated and the readability of the code will decrease.

For example, in the real person SDK project, all the process products in the business chain are included in this category by creating an object, similar to the following code:

RealIdentityChainOutputs {
        // start 过程产物
    public StartOutput mStartOutput;
        // upload 过程产物
    public UploadOutput mUploadOutput;
        // verify 过程产物
    public VerifyOutput mVerifyOutput;
        // submit 最终结果产物
    public SubmitOutput mSubmitOutput;
    
}

The advantage of writing this way is that the process products can be uniformly transferred through a class object by passing an object, just like I took a file bag containing various documents in the hospital, and every time I complete a process Fill up one of the documents and enter the next process, which is simple and convenient. But the problems are also obvious.

First of all, it will bring about the visibility risk of the code. The first few chain nodes already know the data structure of the next few chain node products. Is it possible that the first few nodes have the ability to modify the subsequent node products? Secondly, if two identical product objects appear during the chain transfer process, it is difficult to create two objects with the same data "elegantly" according to the current product packaging class. Whether to create a List or create a new one with the same data Class object? Third, each node has the ability to end the current circulation process, which is also a result of the final product of chain circulation, but if it is placed in the above packaging category, it means that a certain product is the final product of the entire chain. It is contrary to the original intention of defining this packaging category. Of course, these issues are all considered from the perspective of future business expansion, and are available for relatively simple business scenarios for real people. Asking too many questions is suspected of "over-designing."

So what problem does the chain of responsibility solve?

  1. Pre-check, reduce unnecessary post-code logic
  2. The logical decoupling of the sender (sender) and the receiver (receiver(s)) to improve code flexibility is the most important
  3. Requests are transmitted sequentially through the link, which also makes each chain node's responsibilities clear and conforms to the principle of single responsibility
  4. By changing the members of the chain or mobilizing their order, it allows you to add or delete dynamically, which also improves the flexibility of the code

Basic expression of the code of the chain of responsibility pattern

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

image.png

From the most basic UML diagram, it can be seen that there are generally 4 roles in the responsibility chain:

  1. Client client, define the rules and operation mode of the chain, dynamically generate the chain according to the specific business scenario (so the chain is not fixed, you can customize the combination, and select the chain head and chain tail)
  2. Handler processor interface, mainly used to declare the general capabilities of specific processors, generally including abstract processing capabilities and the ability to point to the next node
  3. BaseHandler Basic processor, this is an optional role, you can put some common logic in the specific processor into this category according to the business scenario
  4. ConcreteHandlers specific processor constitutes the processing node in the chain. The core function is to process the request and determine whether the request is consumed at this node or passed along the chain (the specific processors are independent and immutable)

It can be seen that the core logic of the chain of responsibility model is processing and delivery, and at the same time it has the ability to be flexibly customized by the outside.

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

  1. The statement Handler interface defines the interface handled by the node
  2. Eliminate duplication of template code between specific processors by creating an abstract processor base class
  3. Create specific processor subclasses and their implementation methods in turn, and determine whether the current processing class will consume the request or continue to pass along the chain through the specific processing class
  4. Finally reflected in the business layer, Client object self-assembled to realize the decoupling of logical processing and calling objects
// 处理者接口声明了一个创建处理者链的方法。还声明了一个执行请求的方法。
interface Handler is
    method handle()
    method setNext(h: Handler)


// 简单组件的基础类。
abstract class BaseHandler implements Handler is

    field canHandle: boolean

    // 如果组件能处理请求,则处理
    method handle() is
            doCommonThings
    method setNext(h: Handler)



// 原始组件应该能够使用帮助操作的默认实现
class ConcreteHandlerA extends BaseHandler is
    // ...


// 复杂处理者可能会对默认实现进行重写
class ConcreteHandlerB extends BaseHandler is

    method handle() is
        if (canHandle)
            // 处理者B的处理方式
        else
            super.handle()

// ...同上...
class ConcreteHandlerC extends BaseHandler is
    field wikiPageURL: string

    method handle() is
        if (canHandle)
                // 处理者C的处理方式
        else
            super.handle()


// Client
class Application is
    // 每个程序都能以不同方式对链进行配置。
    method cor() is
        handlerA = new ConcreteHandlerA()
        handlerB = new ConcreteHandlerB()
        handlerC = new ConcreteHandlerC()
        // ...
        handlerA.setNext(handlerB)
        handlerB.setNext(handlerC)

Applicable scene

From the above description, we can also see that in fact, as long as it involves processing in a logical order, it can be processed using the chain of responsibility model. However, starting from the actual scenario, deciding whether to use this mode should consider two factors:

  1. Is the scene complicated enough, and is the logic chain very long?
  2. Whether there are flexible and changing business change scenario requirements

At the same time, we must also pay attention to the three problems that inevitably bring about using the responsibility chain:

  1. The number of processors. For the traversal of request processors in the chain, if there are too many processors, the traversal will definitely affect performance, especially in some recursive calls, so be careful
  2. When there is a problem with the code, it is not easy to observe the characteristics of the runtime, which hinders the troubleshooting
  3. The cover request has not been processed even if it is passed to the end of the chain, which causes some abnormal problems

The following is an example of how the chain of responsibility is used with the help of examples related to the ViewGroup
Let's take a look at the delivery path of the act of clicking once on the screen in the Android source code.

image.png

It can be clearly seen that the event delivery and distribution of the Android system is also achieved through the chain. If Activity , ViewGroups , and View are used as specific processors dispatchTouchEvent() method, then it is a standard chain of responsibility model.

// 伪代码逻辑
public boolean dispatchTouchEvent(MotionEvent ev) {

    if(onInterceptTouchEvent(ev)) {
            // onInterceptTouchEvent 方法作为是否需要在本处理者中被消费的判断,如果为 true,则在本控件中消费
            this.onTouchEvent(ev);
    } else {
            // 本控件不被拦截,则传递给下一个控件的 dispatchTouchEvent 方法当中
            next.dispatchTouchEvent(ev);
    }

}

Careful students may find a problem, that is, when the user's click event is passed to the top of the control View , if the View has not been consumed, then it will be returned according to the original link passed over. to place the very beginning of the call chain, from View of onTouch() or onTouchEvent() back Activity of onTouchEvent() method.

When describing the possible problems of the chain of responsibility model in the previous article, we also mentioned that this model has a special point to note is that if the request to the end of the chain has not been processed, it is very likely to make the code stable. problem. Android solves this problem by returning the request to the original chain node again. The advantages of this are:

  1. The request is controllable no matter where it goes (that is, it will be processed, even if it may end up being empty)
  2. Make UI-related functional classes have consistent behaviors (make Activity , ViewGroup , View have the ability to distribute and consume)

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

Let me list some scenarios of the chain of responsibility mode used in the daily development process. The details are not shown:

  • Login module (adjust the logical combination of various pre-account verification through the chain of responsibility)
  • Accounting reimbursement system (the difference of authority is used as the approval of whether to proceed to the next level of processing)
  • Mail filtering system (filtering and intercepting mail attributes, such as important mail, advertising mail, spam, virus mail and other types of mail)

The relationship between the chain of responsibility model and other models

Design pattern does not simply refer to a design pattern that exists independently in code engineering, but a very "rich" product after multiple design patterns are combined, transformed, and adapted according to different business scenarios. Then it has a relationship with the chain of responsibility pattern. What are the design patterns that are relatively close, or that can cooperate with each other?

Through the two roles in the chain of responsibility model, the sender and the receiver, it can be seen that it is similar to the command mode and the intermediary mode. For example, the command mode also establishes a one-way connection between the sender and the requester. , The difference is that the command mode is more inclined to solve scenarios such as parameterized objects and operation rollbacks. Of course, the chain of responsibility mode can be implemented in the command mode.

The intermediary model changes the direct connection between the sender and the receiver to an intermediary object connection, which reduces the messy dependency between objects.
The relationship between design patterns is the relationship of cooperation and transformation, and there are many details in it, which are not the core of this article, so I will not show it here for the time being. This point is also mentioned to allow interested readers to explore on their own.

Summarize

We use design patterns from the perspective of code scalability, code stability, and code readability.

For the chain of responsibility model, it solves the problem of coupling between front and back logic in complex logic scenarios. At the same time, it is also a solution with reference value for business scenarios that need to flexibly respond to changes. When we use this mode, we need to pay special attention to the behavior of the intermediate chain node after it is thrown after consumption and the special scenario where the request at the end of the chain is not processed.

Write at the end

Finally, I borrowed the description of how to use design patterns in the book "Head First Design Patterns" to make a conclusion, and I am deeply convinced.

  1. Extend usage patterns for actual needs, don’t use patterns just for imaginary needs
  2. Simplicity is king, if you can design a simpler solution without a pattern, then go for it
  3. Models are tools, not rules, and need to be adjusted appropriately to meet actual needs

refer to

Author: ES2049 / Dawn
The article can be reprinted at will, but please keep this link to the original text.
You are very welcome to join ES2049 Studio if you are passionate. Please send your resume to caijun.hcj@alibaba-inc.com

ES2049
3.7k 声望3.2k 粉丝