Responsibility chain model

Posted by we4freelance on Sat, 25 Dec 2021 01:12:56 +0100

1. Basic concepts

1. Definition

Enables multiple objects to process requests, thereby avoiding coupling between the sender and receiver of the request. Connect these objects into a chain and pass requests along that chain until one object processes it.

4. Examples of similar backgrounds

Request process, need main procedure approval within 1 day, project manager approval within 3 days, boss approval for more than 3 days;

3. Key Points

Decouple the requestor and the handler. The requestor does not know how the request is processed. The handler consists of independent subprocesses, which are linked by a chain table and can be combined in any order.
Chain of responsibility requests emphasize that requests are ultimately handled by a subprocess. Judged by each sub-processing condition;
The responsibility chain extension is the function chain, which emphasizes that a request is processed through sub-processes in the function chain in turn.
By abstracting the functions and their sequence, the changes of duties can be expanded arbitrarily, and the order of duties can be expanded arbitrarily.

2. Structure Diagram


HandleA and HandleB are loosely coupled by pointer direction. I don't understand the structure diagram at first, but when I look at it again when the code understands it, I suddenly realize.

3. Explanation of Examples

1. Common code

For example, we need to simulate the leave Approval Mode in the company, first pass the leave slip to the upper level leaders, and then submit the leave slip to the upper level if they have no right to approve it.
Normal code:

#include<iostream>
#include <string>
using namespace std;

class Context {
public:
    std::string name;
    int day;
};

class LeaveRequest {
public:
    // LeaveRequest class becomes unstable as judgement increases
    bool HandleRequest(const Context& ctx) {
        if (ctx.day <= 1)
            return HandleByMainProgram(ctx);
        else if (ctx.day <= 3)
            return HandleByProjMgr(ctx);
        else
            return HandleByBoss(ctx);
    }

private:
    bool HandleByMainProgram(const Context& ctx) {
        cout << "Main Procedure Approval" << endl;
        return true;
    }
    bool HandleByProjMgr(const Context& ctx) {
        cout << "Project Manager Approval" << endl;
        return true;
    }
    bool HandleByBoss(const Context& ctx) {
        cout << "Boss approval" << endl;
        return true;
    }
};

int main()
{
    LeaveRequest LR;

    Context A;
    A.name = "xiaohua";
    A.day = 1;
    LR.HandleRequest(A);
    
    Context B;
    B.name = "xiaomeng";
    B.day = 3;
    LR.HandleRequest(B);

    return 0;
}

Common Code Disadvantage: Everyone and how they are handled are encapsulated in a class. And if someone else's responsibilities change (for example, the power to experience a project extends to grant 10 days of vacation), then we need to modify this whole category. In addition, if you add new jobs in the future (For example, if a new boss's secretary is added and the project manager's Secretary can approve the vacation request), then we also need to modify the add code in the original big class. This obviously violates the open-close principle and is highly coupled because it is sold in one class. This can be solved by analogizing the actual situation reported by the middle level of reality with the use of the chain table, that is Responsibility chain model.

2. Responsibility chain code

Responsibility chain pattern code:

#include<iostream>
#include <string>
using namespace std;

class Context {
public:
    string name;
    int day;
};

class IHandler {
public:
    void SetNextHandler(IHandler* Handler){
        this->next = Handler;
    }
    bool Handler(const Context& ctx) {
        //Can approve on own
        if (CanHandler(ctx)) {
            return HandlerRequest(ctx);
        }
        //Submit it to the supervisor if it cannot be approved
        else if(GetNextHandler()){
            return GetNextHandler()->Handler(ctx);
        }
        //At the top level, there's still no approval
        else {
            cout << "Your vacation is unapproved" << endl;  //or  other
        }
        return false;
    }
    virtual ~IHandler() {}
protected:
    virtual bool HandlerRequest(const Context& ctx) = 0;
    virtual bool CanHandler(const Context& ctx) = 0;
    IHandler* GetNextHandler() {
        return this->next;
    }
private:
    IHandler* next;
};

//One-day vacation is approved directly in the main program
class HandlerByMainProgram :public IHandler{
public:
    bool CanHandler(const Context& ctx) {
        return ctx.day <= 1;
    }
    bool HandlerRequest(const Context& ctx) {
        cout << "Main Procedure Approval" << endl;
        return true;
    }
 
};

//2-3 days vacation approved by Project Manager
class HandlerByProjMgr :public IHandler {
public:
    bool CanHandler(const Context& ctx) {
        return ctx.day >= 2 && ctx.day <= 3;
    }
    bool HandlerRequest(const Context& ctx) {
        cout << "Project Manager Approval" << endl;
        return true;
    }
};

//4-20 Vacation Requires Boss Approval
class HandlerByBoss :public IHandler {
public:
    bool CanHandler(const Context& ctx) {
        return ctx.day >= 4 && ctx.day <= 20;
    }
    bool HandlerRequest(const Context& ctx) {
        cout << "Boss approval" << endl;
        return true;
    }
};

//Holidays of less than 30 days can be approved by the Secretary of the boss
class HandlerBySecretary :public IHandler{
public:
    bool CanHandler(const Context& ctx) {
        return ctx.day <= 30;
    }
    bool HandlerRequest(const Context& ctx) {
        cout << "The Secretariat has taken care of your holidays" << endl;
        return true;
    }
};

int main()
{
    IHandler* H0 = new HandlerByMainProgram();
    IHandler* H1 = new HandlerByProjMgr();
    IHandler* H2 = new HandlerByBoss();
    IHandler* H3 = new HandlerBySecretary();

    //The order of connections can be disrupted, but normally there are certain requirements
    //For example, the company's vacation system, generally starts with a batch of group leaders, and then goes up one level after another if the group leaders have no authority.
    //If the company has a strict time-off system, disrupting the order of time-off does not affect final approval, but changes who approves it
    //For example, if the secretary is the first one, then all the vacations that can be approved here will be approved by the Secretary (because false secretaries within 30 days can approve them).
    //But often powerful people let their people solve things first, and they solve things they can't get rid of.
    H0->SetNextHandler(H1);
    H1->SetNextHandler(H2);
    H2->SetNextHandler(H3);
    Context A;
    A.name = "xiaomeng";
    A.day = 2;
    H0->Handler(A);

    Context B;
    B.name = "xiaohua";
    B.day = 15;
    H0->Handler(B);
    
    Context C;
    C.name = "xiaopang";
    C.day = 40;//Operation not handling 40 days vacation at this time
    H0->Handler(C);

    return 0;
}

Run result:

You can see that the different leaders are decoupled, not completely decoupled, and implicitly linked through a chain table, which greatly reduces their coupling. A leader's power change does not require the code of other leaders to be modified.

Topics: C++ Design Pattern