[Architecture-Design Mode] Responsibility Chain Mode

Posted Jun 27, 202011 min read

In a card game like "Landlord", someone plays a card to his next house. The next house looks at the cards in his hand. If he can't afford the other house cards, he forwards the card request to his next house. , And then make judgments at the next house. After a loop, if no one else can afford the card, the original player can play a new card. In this process, cards are passed along a chain as a request, and each player of the card can handle the request. In the design pattern, we also have a model specifically for handling this kind of request chain transfer, which is the chain of responsibility pattern.

Overview

In many cases, more than one object can be processed in a software system, such as purchase order approval in the SCM system. The director, vice chairman, chairman and board of directors can all process the purchase order. The single chain structure, the purchase order is transmitted along this chain, this chain is called the responsibility chain. The chain of responsibilities can be a straight line, a ring, or a tree structure. The most common chain of responsibilities is a linear type, which transmits requests along a unidirectional chain. Each object on the chain is a request processor. The chain of responsibility mode can organize the request processor into a chain and let the request pass along the chain. The processor on the chain processes the request accordingly, and the client does not need to Concerned about the processing details of the request and the transfer of the request, you only need to send the request to the chain to realize the decoupling of the request sender and the request processor.

The chain of responsibility model is defined as follows:

Chain of Responsibility Pattern:avoid the coupling of the request sender and receiver, so that multiple objects are likely to receive the request, connect these objects into a chain, and pass the request along the chain Until an object handles it. The chain of responsibility pattern is an object behavior pattern.

The core of the responsibility chain pattern structure is the introduction of an abstract processor. The structure of the chain of responsibility pattern is shown in the figure:

1333307613_2407.gif

The following roles are included in the structure diagram of the chain of responsibility pattern:

  • Handler(abstract processor):It defines an interface for processing requests, and is generally designed as an abstract class. Because different concrete processors process requests in different ways, abstract request processing methods are defined in them. Because each processor's next family is still a processor, an abstract processor type object(such as a successor in the structure diagram) is defined in the abstract processor as its reference to the next family. Through this reference, the processor can be connected into a chain.
  • ConcreteHandler(concrete handler):It is a subclass of abstract handler, which can handle user requests. In the specific handler class, the abstract request processing method defined in the abstract handler is implemented, and judgment is required before processing the request. See if there is corresponding processing authority. If it can process the request, process it, otherwise forward the request to the successor; in the specific processor, you can access the next object in the chain to forward the request.

In the chain of responsibility model, many objects are connected by each object's reference to its next family to form a chain. The request is passed on the chain until an object on the chain decides to process the request. The client that issued the request does not know which object on the chain will finally process the request, which allows the system to dynamically reorganize the chain and assign responsibilities without affecting the client.

The core of the responsibility chain pattern lies in the design of the abstract processor class. The typical code of the abstract processor is as follows:

abstract class Handler
{
    //Maintain a reference to the next home
    protected $successor;

    public function setSuccessor(Handler $successor)
    {
        $this->successor = $successor;
    }

    abstract public function handleRequest(string $request);
}

In the above code, the abstract handler class defines a reference object to Xiajia in order to forward the request to Xiajia. The access symbol of this object can be set to protected and can be used in its subclasses. An abstract request processing method is declared in the abstract handler class, and the specific implementation is left to the subclass.

The concrete processor is a subclass of the abstract processor, which has two major functions:the first is processing requests, and different concrete processors implement the abstract request processing method handleRequest() in different forms; the second is Forward the request, if the request exceeds the permissions of the current processor class, you can forward the request to the next home. The typical code of the specific processor class is as follows:

class ConcreteHandler extends Handler
{
    public function handleRequest(string $request)
    {
        if(the request condition is met) {
            //Process the request
        } else {
            //Forward the request
            $this->successor->handleRequest($request);
        }
    }
}

It should be noted that the responsibility chain model does not create a responsibility chain. The creation of the responsibility chain must be completed by other parts of the system. Generally, the responsibility chain is created in the client using the responsibility chain. The chain of responsibility pattern reduces the coupling between the sender and receiver of the request, so that multiple objects have the opportunity to process the request.

Case

Sunny Software has undertaken the development task of an enterprise's SCM(Supply Chain Management) system, which includes a procurement approval subsystem. The purchase approval of the enterprise is carried out in stages, that is, according to the purchase amount, the supervisors at different levels approve, the director can approve purchase orders below 50,000 yuan(excluding 50,000 yuan), and the vice chairman can approve 50,000 For purchase orders ranging from RMB 100,000 to RMB 100,000(not including RMB 100,000), the chairman can approve purchase orders ranging from RMB 100,000 to RMB 500,000(excluding RMB 500,000). For purchase orders of RMB 500,000 and above, the board of directors must be opened. Discuss the decision. as the picture shows:

1333307283_7751.gif

If a processing class PurchaseRequestHandler is used to uniformly process all approval requests, it will cause the following problems:

  1. The PurchaseRequestHandler class is relatively large, and the approval methods of all levels are concentrated in one class, which violates the "single responsibility principle" and is difficult to test and maintain.
  2. If you need to add a new approval level or adjust the approval amount and details of any level(for example, change the approval limit of the chairman to 600,000 yuan), you must modify the source code and conduct rigorous testing. In addition, if necessary When removing a certain level(for example, a purchase order with an amount of 100,000 yuan or more is directly approved by the chairman and no longer has the position of vice chairman), the source code must also be modified, which violates the "opening and closing principle".
  3. The setting of the approval process lacks flexibility. The current approval process is "Director-->Vice Chairman-->Chairman-->Board". If necessary, change to "Director-->Chairman-->Board" In this solution, it can only be achieved by modifying the source code, and the client cannot customize the approval process.

In order to make the purchase order approval process more flexible, and realize the chain transfer and processing of purchase orders, Sunny developers use the chain of responsibility model to implement the hierarchical approval of purchase orders. Its basic structure is shown in the figure:

1333307860_9326.gif

The abstract class Approver acts as an abstract handler(abstract passer), Director, VicePresident, President, and Congress act as a concrete handler(concrete passer), and PurchaseRequest acts as a request class. The complete code is as follows:

<?php

//Purchase order:request
class PurchaseRequest
{
    private $amount; //purchase amount
    private $number; //purchase order number
    private $purpose; //purchase purpose

    public function __construct(float $amount, int $number, string $purpose)
    {
        $this->amount = $amount;
        $this->number = $number;
        $this->purpose = $purpose;
    }

    public function setAmount(float $amount)
    {
        $this->amount = $amount;
    }

    public function getAmount():float
    {
        return $this->amount;
    }

    public function setNumber(int $number)
    {
        $this->number = $number;
    }

    public function getNumber():int
    {
        return $this->number;
    }

    public function setPurpose(string $purpose)
    {
        $this->purpose = $purpose;
    }

    public function getPurpose():string
    {
        return $this->purpose;
    }
}

//Approver class:abstract processor
abstract class Approver
{
    protected $successor; //Define the successor object
    protected $name; //Approver's name

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    //Set successor
    public function setSuccessor(Approver $successor)
    {
        $this->successor = $successor;
    }

    //Abstract request processing method
    abstract public function processRequest(PurchaseRequest $request);
}

//Director:specific handler
class Director extends Approver
{
    public function __construct(string $name)
    {
        parent::__construct($name);
    }

    //The specific request processing method
    public function processRequest(PurchaseRequest $request)
    {
        if($request->getAmount() <50000) {
            //Process the request
            echo'Director's approval';
        } else {
            //Forward the request
            $this->successor->processRequest($request);
        }
    }
}

//Vice chairman category:specific processor
class VicePresident extends Approver
{
    public function __construct(string $name)
    {
        parent::__construct($name);
    }

    //The specific request processing method
    public function processRequest(PurchaseRequest $request)
    {
        if($request->getAmount() <100000) {
            //Process the request
            echo'Vice Chairman Approval';
        } else {
            //Forward the request
            $this->successor->processRequest($request);
        }
    }
}

//Chairman category:specific processor
class President extends Approver
{
    public function __construct(string $name)
    {
        parent::__construct($name);
    }

    //The specific request processing method
    public function processRequest(PurchaseRequest $request)
    {
        if($request->getAmount() <500000) {
            //Process the request
            echo'Chairman's approval';
        } else {
            //Forward the request
            $this->successor->processRequest($request);
        }
    }
}

//Board category:specific processor
class Congress extends Approver
{
    public function __construct(string $name)
    {
        parent::__construct($name);
    }

    //The specific request processing method
    public function processRequest(PurchaseRequest $request)
    {
        echo'Convene the approval of the board of directors';
    }
}

class Client
{
    public function test()
    {
        $director = new Director('   ');
        $vicePresident = new VicePresident('   ');
        $president = new President('   ');
        $congress = new Congress('Board');

        //Create a chain of responsibility
        $director->setSuccessor($vicePresident);
        $vicePresident->setSuccessor($president);
        $president->setSuccessor($congress);

        //Create purchase order
        $pr1 = new PurchaseRequest(45000, 10001,'Purchase desk');
        $director->processRequest($pr1);

        $pr2 = new PurchaseRequest(60000, 10002,'Buy printer');
        $director->processRequest($pr2);

        $pr3 = new PurchaseRequest(160000, 10003,'Buy desktop');
        $director->processRequest($pr3);

        $pr4 = new PurchaseRequest(800000, 10004,'Purchase server');
        $director->processRequest($pr4);
    }
}

If you need to add a new specific processor to the system, such as adding a Manager role, you can approve a purchase order of 50,000 to 80,000 yuan(not including 80,000 yuan), you need to write a new specific processor class Manager As a subclass of the abstract processor class Approver, implement the abstract processing method defined in the Approver class. If the purchase amount is greater than or equal to 80,000 yuan, the request will be forwarded to the next home.

Since the creation process of the chain is the responsibility of the client, adding a new specific processor class has no effect on the original class library, and there is no need to modify the source code of the existing class, which complies with the "opening and closing principle".

In the client code, if you want to apply a new specific request processor to the system, you need to create a new specific processor object, and then add the object to the chain of responsibility.

Pure and impure chain of responsibility model

The chain of responsibility mode can be divided into two types:pure chain of responsibility mode and impure chain of duty mode:

Pure responsibility chain mode

A pure chain of responsibility model requires that a specific processor object can only choose one of two behaviors:Either assume full responsibility or push the responsibility to the next family, no specific processor object is allowed to assume After some or all of the responsibilities are passed down. And in the pure chain of responsibility model, requires that a request must be received by a certain processor object, and it cannot happen that a request is not processed by any processor object. In the previous purchase order approval example, the pure chain of responsibility model was applied.

Impure chain of responsibility model

In an impure chain-of-responsibility model Allows a request to be partially processed by a specific processor before being passed down, or after a specific processor processes a request, subsequent processors can continue to process the request, and a The request may eventually not be received by any processor object. The event processing model in Java AWT 1.0 applies an impure chain of duty model. The basic principle is as follows:since window components(such as buttons, text boxes, etc.) are generally located in container components, when an event occurs on a certain component When the event is passed to the corresponding event processing method through the handleEvent() method of the component object, the event processing method will process the event, and then decide whether to propagate the event to the upper-level container component; the upper-level container component receives the event Then you can continue to process this event and decide whether to continue to propagate to the upper-level container component, and so on, until the event reaches the top-level container component; if there is no processing method until the top-most container, the event will not be processed. Each component of the level can handle this event when it receives the event, regardless of whether the event has been processed at the previous level, and there are still cases where the event has not been processed. Obviously, this is the impure chain of responsibility model. The event processing mechanism in the early Java AWT event model(JDK 1.0 and earlier) is also called the event bubbling mechanism. Since Java.1.1, JDK uses observer mode instead of chain of responsibility mode to handle events. At present, this event floating mechanism can still be used for event processing in JavaScript.

to sum up

The responsibility chain mode organizes the request processor by establishing a chain. The request will be transmitted along the chain. The request sender does not need to know when, where and how the request is processed, which realizes the decoupling of the request sender and the processor . In software development, if you encounter multiple objects that can handle the same request, you can apply the chain of responsibility pattern. For example, create a filter chain in Web application development to filter the request data and work In the stream system, the graded approval of official documents, etc., can be solved by using the chain of responsibility model.

main advantage
  1. The chain of responsibility model makes it unnecessary for an object to know which other object is processing its request. The object only needs to know that the request will be processed. The receiver and sender do not have explicit information about each other, and the objects in the chain do not need to Knowing the structure of the chain, the client is responsible for the creation of the chain, which reduces the coupling of the system.
  2. The request processing object only needs to maintain a reference to its successor, and does not need to maintain its reference to all candidate processors, which can simplify the interconnection of objects.
  3. When assigning responsibilities to objects, the chain of responsibilities can give us more flexibility. You can increase or change the responsibilities of handling a request by dynamically adding or modifying the chain at runtime.
  4. When a new specific request processor is added to the system, there is no need to modify the code of the original system, it is only necessary to re-establish the chain on the client. From this point of view, it is in line with the "opening and closing principle".
Main disadvantages
  1. Since a request does not have a clear receiver, there is no guarantee that it will be processed. The request may not be processed until the end of the chain; a request may not be obtained because the responsibility chain is not configured correctly deal with.
  2. For a relatively long chain of responsibilities, the processing of requests may involve multiple processing objects, system performance will be affected to some extent, and it is not convenient when debugging code.
  3. If the chain is not properly established, it may cause a loop call, which will cause the system to fall into an infinite loop.
Applicable scene
  1. There are multiple objects that can process the same request. The specific object that handles the request will be determined at the time of execution. The client only needs to submit the request to the chain without having to care who the request processing object is and how it is processed. .
  2. Without specifying the recipient, submit a request to one of the multiple objects.
  3. A group of objects can be dynamically designated to process the request. The client can dynamically create a chain of responsibilities to process the request, and can also change the order between the processors in the chain.