On the responsibility chain model of design model

Posted by agge on Thu, 23 Dec 2021 07:35:35 +0100

Responsibility chain model

1, Definition

Responsibility chain pattern: multiple objects have the opportunity to process the request, so as to avoid the coupling relationship between the sender and receiver of the request. Connect these objects into a chain and pass the request along the chain until an object has finished processing it.

2, Usage scenario

Each of us bears certain responsibilities in our work. For example, programmers are responsible for developing new functions and modifying bug s, operators are responsible for publicity, and HR is responsible for recruiting new people. Does each of our responsibilities have anything to do with this chain of responsibility?

——The answer is that it doesn't matter much.

But it doesn't matter at all, mainly because everyone's responsibilities in different posts are decentralized. The combination of decentralized responsibilities is more like a network and can't form a chain.

The responsibilities of the same position can form a chain.

For example, ordinary programmers can solve moderately difficult bugs, excellent programmers can solve difficult bugs, while rookie programmers can only solve simple bugs. In order to quantify it, we use a number to represent the difficulty of a bug, (0,20] means simple, (20,50] means medium, (50100] means difficult). Let's simulate the process of solving a bug.

1. Bug resolver 1.0

/**
 *	Create a new bug class:
 */
public class Bug {
    // Difficulty value of bug
    int value;

    public Bug(int value) {
        this.value = value;
    }
}

/**
 *	Create a new programmer class:
 */

public class Programmer {
    // Programmer type: rookie, ordinary, excellent
    public String type;

    public Programmer(String type) {
        this.type = type;
    }

    public void solve(Bug bug) {
        System.out.println(type + "The programmer solved a difficulty for " + bug.value + " of bug");
    }
}

/**
 *	client:
 */
import org.junit.Test;

public class Client {
    @Test
    public void test() {
        Programmer newbie = new Programmer("green hand");
        Programmer normal = new Programmer("ordinary");
        Programmer good = new Programmer("excellent");

        Bug easy = new Bug(20);
        Bug middle = new Bug(50);
        Bug hard = new Bug(100);

        // Try to solve the bug in turn
        handleBug(newbie, easy);
        handleBug(normal, easy);
        handleBug(good, easy);

        handleBug(newbie, middle);
        handleBug(normal, middle);
        handleBug(good, middle);

        handleBug(newbie, hard);
        handleBug(normal, hard);
        handleBug(good, hard);
    }

    public void handleBug(Programmer programmer, Bug bug) {
        if (programmer.type.equals("green hand") && bug.value > 0 && bug.value <= 20) {
            programmer.solve(bug);
        } else if (programmer.type.equals("ordinary") && bug.value > 20 && bug.value <= 50) {
            programmer.solve(bug);
        } else if (programmer.type.equals("excellent") && bug.value > 50 && bug.value <= 100) {
            programmer.solve(bug);
        }
    }
}

The code logic is very simple. We let three types of programmers try to solve the bug in turn. If the difficulty of the bug is within the scope they can solve, they can deal with the bug themselves.

Run the program and the output is as follows:

Rookie programmer solved a problem with difficulty of 20 bug
 Ordinary programmers solve a problem with a difficulty of 50 bug
 Excellent programmers solve a problem with a difficulty of 100 bug

There is no problem with the output, indicating that the function has been perfectly realized. However, in this program, we let every programmer try to deal with every bug, which is equivalent to everyone discussing who should solve each bug. This is undoubtedly a very inefficient practice. So how can we optimize it?

2. Bug resolver 2.0

In fact, many companies choose to let the project manager assign tasks, and the project manager will assign different people to solve the bug according to the difficulty of the bug.

Introduce the ProjectManager class:

public class ProjectManager {
    Programmer newbie = new Programmer("green hand");
    Programmer normal = new Programmer("ordinary");
    Programmer good = new Programmer("excellent");

    public void assignBug(Bug bug) {
        if (bug.value > 0 && bug.value <= 20) {
            System.out.println("The project manager will this simple bug Assigned to rookie programmers");
            newbie.solve(bug);
        } else if (bug.value > 20 && bug.value <= 50) {
            System.out.println("The project manager will this medium bug Assigned to ordinary programmers");
            normal.solve(bug);
        } else if (bug.value > 50 && bug.value <= 100) {
            System.out.println("The project manager will this difficult problem bug Assigned to good programmers");
            good.solve(bug);
        }
    }
}

We let the project manager manage all the programmers and assign tasks according to the difficulty of the bug. In this way, all bugs only need to be transmitted to the project manager for allocation. The modified client is as follows:

import org.junit.Test;

public class Client2 {
    @Test
    public void test() {
        ProjectManager manager = new ProjectManager();

        Bug easy = new Bug(20);
        Bug middle = new Bug(50);
        Bug hard = new Bug(100);

        manager.assignBug(easy);
        manager.assignBug(middle);
        manager.assignBug(hard);
    }
}

Run the program and the output is as follows:

The project manager will this simple bug Assigned to rookie programmers
 Rookie programmer solved a problem with difficulty of 20 bug
 The project manager will this medium bug Assigned to ordinary programmers
 Ordinary programmers solve a problem with a difficulty of 50 bug
 The project manager will this difficult problem bug Assigned to good programmers
 Excellent programmers solve a problem with a difficulty of 100 bug

It looks good, except that the project manager is scolding and refuting the scheme.

In this modified program, the project manager undertakes the manual work of assigning all bug s alone. The program did not become concise, but transferred the complex logic from the client to the project manager class.

Moreover, the project manager class assumes too many responsibilities. If a new type of programmer is added in the future, the project manager class must be changed and its responsibility for handling bug s must be inserted into the branch judgment statement, which violates the single responsibility principle and the opening and closing principle.

Therefore, we need a better solution, that is, the responsibility chain model!

3. Bug resolver 3.0

In the scenario of this example, the responsibility of each programmer is to "solve this bug". When a bug is raised by the test, such a responsibility chain can be followed:

  • First hand it over to the novice programmer. If it is a simple bug, the novice programmer will deal with it by himself. If the bug is too difficult for novice programmers, give it to ordinary programmers
  • If it is a moderately difficult bug, ordinary programmers can deal with it. If he can't solve it, give it to a good programmer
  • Good programmers deal with difficult bug s
    Some readers will ask, what if good programmers can't handle this bug?

——That is, of course, to get rid of this fake good programmer.

Modify the client as follows:

import org.junit.Test;

public class Client3 {
    @Test
    public void test() throws Exception {
        Programmer newbie = new Programmer("green hand");
        Programmer normal = new Programmer("ordinary");
        Programmer good = new Programmer("excellent");

        Bug easy = new Bug(20);
        Bug middle = new Bug(50);
        Bug hard = new Bug(100);

        // Chain transmission responsibility
        if (!handleBug(newbie, easy)) {
            if (!handleBug(normal, easy)) {
                if (!handleBug(good, easy)) {
                    throw new Exception("Kill the fake good programmer!");
                }
            }
        }

        if (!handleBug(newbie, middle)) {
            if (!handleBug(normal, middle)) {
                if (!handleBug(good, middle)) {
                    throw new Exception("Kill the fake good programmer!");
                }
            }
        }

        if (!handleBug(newbie, hard)) {
            if (!handleBug(normal, hard)) {
                if (!handleBug(good, hard)) {
                    throw new Exception("Kill the fake good programmer!");
                }
            }
        }
    }

    public boolean handleBug(Programmer programmer, Bug bug) {
        if (programmer.type.equals("green hand") && bug.value > 0 && bug.value <= 20) {
            programmer.solve(bug);
            return true;
        } else if (programmer.type.equals("ordinary") && bug.value > 20 && bug.value <= 50) {
            programmer.solve(bug);
            return true;
        } else if (programmer.type.equals("excellent") && bug.value > 50 && bug.value <= 100) {
            programmer.solve(bug);
            return true;
        }
        return false;
    }
}

Three nested if conditional sentences form a rookie - > ordinary - > excellent responsibility chain. We make the handleBug method return a boolean value. If the bug is handled, it returns true; Otherwise, false will be returned to make the responsibility continue to pass along the rookie - > ordinary - > excellent chain. This is the idea of the responsibility chain mode.

Run the program and the output is as follows:

Rookie programmer solved a problem with difficulty of 20 bug
 Ordinary programmers solve a problem with a difficulty of 50 bug
 Excellent programmers solve a problem with a difficulty of 100 bug

Students familiar with the responsibility chain model should be able to see that this responsibility chain model is different from what we usually use. In fact, this code has well reflected the basic idea of the responsibility chain model. The responsibility chain pattern we usually use just encapsulates this code on the basis of object-oriented. Then, let's encapsulate this code and turn it into a standard responsibility chain pattern.

4. Bug resolver 4.0

Create a new programmer abstract class:

public abstract class Programmer {
    protected Programmer next;

    public void setNext(Programmer next) {
        this.next = next;
    }

    abstract void handle(Bug bug);
}

In this abstract class:

  • The next object indicates that if you can't solve it yourself, you need to transfer the responsibility to the next person.
  • The handle method represents the logic for handling this bug. Here, it is judged that it is resolved or continued to pass.

New rookie programmer class:

public class NewbieProgrammer extends Programmer {

    @Override
    public void handle(Bug bug) {
        if (bug.value > 0 && bug.value <= 20) {
            solve(bug);
        } else if (next != null) {
            next.handle(bug);
        }
    }

    private void solve(Bug bug) {
        System.out.println("Rookie programmer solved a difficulty for " + bug.value + " of bug");
    }
}

Create a new ordinary programmer class:

public class NormalProgrammer extends Programmer {

    @Override
    public void handle(Bug bug) {
        if (bug.value > 20 && bug.value <= 50) {
            solve(bug);
        } else if (next != null) {
            next.handle(bug);
        }
    }

    private void solve(Bug bug) {
        System.out.println("Ordinary programmers solve a difficulty for " + bug.value + " of bug");
    }
}

Create a new excellent programmer class:

public class GoodProgrammer extends Programmer {

    @Override
    public void handle(Bug bug) {
        if (bug.value > 50 && bug.value <= 100) {
            solve(bug);
        } else if (next != null) {
            next.handle(bug);
        }
    }

    private void solve(Bug bug) {
        System.out.println("Excellent programmers solve a difficulty for " + bug.value + " of bug");
    }
}

Client test:

import org.junit.Test;

public class Client4 {
    @Test
    public void test() {
        NewbieProgrammer newbie = new NewbieProgrammer();
        NormalProgrammer normal = new NormalProgrammer();
        GoodProgrammer good = new GoodProgrammer();

        Bug easy = new Bug(20);
        Bug middle = new Bug(50);
        Bug hard = new Bug(100);

        // Form a chain of responsibility
        newbie.setNext(normal);
        normal.setNext(good);

        // Start with rookie programmers and pass along the responsibility chain
        newbie.handle(easy);
        newbie.handle(middle);
        newbie.handle(hard);
    }
}


On the client side, we use the setNext() method to form a responsibility chain for three programmers. Novice programmers receive all bugs and pass them to ordinary programmers if they find that they can't handle them. After ordinary programmers receive bugs, if they find that they can't solve them, they pass them to excellent programmers. This is the writing method of the standardized responsibility chain model.

The idea of responsibility chain has many applications in life, such as holiday approval, salary increase application, etc. after the employee puts forward the application, start with the manager, and your manager decides to handle it by himself or by a manager at a higher level.

For another example, when dealing with customer complaints, the customer service personnel at the grass-roots level decide to respond or report to the leader, and the leader will judge whether to continue to report.

After clarifying the responsibility chain model, the author suddenly recalled that every time the company's test team raised a bug, it was always assigned to me first! For a moment, I seemed to understand something, and I couldn't help falling into meditation.

3, Summary

Through this example, we have learned that the responsibility chain is mainly used to deal with classes with the same responsibilities and different degrees.

Its main advantages are:

  • Reduces the coupling between objects. In the responsibility chain mode, the customer only needs to send the request to the responsibility chain, and does not need to care about the processing details of the request and the transmission process of the request. Therefore, the responsibility chain decouples the sender of the request from the handler of the request.
  • It has strong expansibility and meets the opening and closing principle. You can add new request processing classes as needed.
  • Flexible. You can dynamically change the members in the chain or change the order of the chain to adapt to the changes of the process.
  • Simplifies the connection between objects. Each object only needs to maintain a reference to its successor without maintaining the references of all other processors, which avoids the use of many conditional judgment statements.
  • Responsibility sharing. Each class only needs to deal with its own work that should be handled, and the work that should not be handled should be transferred to the next object for completion. The scope of responsibility of each class should be defined and in line with the principle of single responsibility of the class. The "project manager" is no longer required to handle all responsibility assignment tasks.

However, we have also found an obvious disadvantage in using it. If this bug is not handled, it may lead to the exception of "programmer worship heaven". Its main disadvantages are:

  • There is no guarantee that every request will be processed. The request may not be processed until it reaches the end of the chain.
  • If the responsibility chain is too long, the request processing may involve multiple processing objects, and the system performance will be affected to some extent.
  • The rationality of the establishment of the responsibility chain depends on the client, which increases the complexity of the client, and may lead to system errors due to the wrong splicing order of the responsibility chain, such as circular calls.

Reference leetcode article address: https://leetcode-cn.com/leetbook/read/design-patterns/9e9bbs/

Topics: Java Design Pattern