Responsibility chain model

Keywords: Design Pattern

1, Introduction to responsibility chain model

1.1 definitions

  • The Chain of Responsibility Pattern creates a chain of receiver objects for requests. This mode gives the type of request and decouples the sender and receiver of the request. This type of design pattern belongs to behavioral pattern.
  • In this pattern, each recipient usually contains a reference to another recipient. If an object cannot process the request, it will pass the same request to the next recipient until an object processes it.

1.2 UML structure diagram of responsibility chain model

1.3 detailed structure of responsibility chain model

  1. Abstract handler role: define an abstract class for processing requests, including abstract processing methods and a subsequent connection.
  2. Concrete handler (ConsoleLogger) role: implement the abstract handler role. When implementing the handler abstract method, judge whether the request can be processed. If it can be processed, process it. Otherwise, transfer the request to its successor.
  3. Client role: create a processing chain and submit a request to the specific handler object of the chain head. It does not care about the processing details and the transmission process of the request.

1.3 use case code implementation

Abstract processing class definition:

public abstract class AbstractLogger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;

    /**
     * log level
     */
    protected Integer level;

    /**
     * The next element in the chain of responsibility
     */
    protected AbstractLogger nextLogger;

    public void setNextLogger(AbstractLogger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public void logMessage(Integer level, String message) {
        if (this.level <= level) {
            write(message);
        }
        //Forward to the next chain of responsibility
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }

    /**
     * Abstract concrete log writing method
     *
     * @param message
     */
    protected abstract void write(String message);
}

Console logger level log class definition:

public class ConsoleLogger extends AbstractLogger {

    public ConsoleLogger(Integer level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: " + message);
    }
}

ErrorLogger level log class definition:

public class ErrorLogger extends AbstractLogger {
    public ErrorLogger(Integer level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}

FileLogger level log class definition:

public class FileLogger extends AbstractLogger {

    public FileLogger(Integer level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}

Test case definition:

public class ClientTest {

    private static AbstractLogger getChainOfLoggers(){

        AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
        AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
        AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

        errorLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(consoleLogger);

        return errorLogger;
    }

    public static void main(String[] args) {
        AbstractLogger loggerChain = getChainOfLoggers();

        loggerChain.logMessage(AbstractLogger.INFO, "info");

        loggerChain.logMessage(AbstractLogger.DEBUG, "debug");

        loggerChain.logMessage(AbstractLogger.ERROR, "error");
    }
}

Expected output:

Connected to the target VM, address: '127.0.0.1:59791', transport: 'socket'
Standard Console::Logger: info
File::Logger: debug
Standard Console::Logger: debug
Error Console::Logger: error
File::Logger: error
Standard Console::Logger: error
Disconnected from the target VM, address: '127.0.0.1:59791', transport: 'socket'

2, Application scenario of responsibility chain mode:

2.1 scenario overview

For example, these front-line E-commerce Internet companies, such as Alibaba, JD and pinduoduo, will do some operational activity scenes and provide capacity expansion preparations during 618, just like Baidu's red envelope during the Chinese New Year. However, all the developed systems need to be launched one after another, because there are sometimes some urgent adjustments to be launched near 618. However, in order to ensure the stability of the online system, the launch will be reduced as much as possible, and the approval will be strengthened accordingly. Just like primary response and secondary response.

The approval process will increase with the addition of different levels of principals at a specific time point, and everyone is like every core point in the responsibility chain model. For R & D partners, you don't need to care about the details of the specific approval process. You just need to know that the launch is stricter and the level is higher. However, for R & D personnel, they also click the same review button and wait for review.
To achieve the goal, the activity days are different and approved by different personnel, 1 day - > operation personnel 3 days - > Operation Manager 6 days - > operation director. The specific implementation depends on the code use case!

2.2 scene mode diagram

Next, according to the idea of object-oriented, we have to define the objects we need to use. We use responsibility chain mode and builder mode to realize the approval process!

2.3 scenario code implementation

Build activity request object (in builder mode)

/**
 * Description: Build request object
 * <br/>
 * Request
 *
 * @author laiql
 * @date 2021/11/2 10:23
 */
public class Request {
    /**
     * Activity id
     */
    private String activityId;
    /**
     * Activity name
     */
    private String activityName;
    /**
     * Activity start time
     */
    private Integer activityTime;
    /**
     * First level approval
     */
    private String levelOne;
    /**
     * Secondary approval
     */
    private String levelTwo;
    /**
     * Three level approval
     */
    private String levelThree;

    public Request(Builder builder) {
        super();
        this.activityId = builder.activityId;
        this.activityName = builder.activityName;
        this.activityTime = builder.activityTime;
        this.levelOne = builder.levelOne;
        this.levelTwo = builder.levelTwo;
        this.levelThree = builder.levelThree;
    }

    /**
     * Use builder mode
     */
    public static class Builder {
        public String activityId;
        public String activityName;
        public Integer activityTime;
        public String levelOne;
        public String levelTwo;
        public String levelThree;

        public Builder() {
        }

        public Builder setActivityId(String activityId) {
            this.activityId = activityId;
            return this;
        }

        public Builder setActivityName(String activityName) {
            this.activityName = activityName;
            return this;
        }

        public Builder setactivityTime(Integer activityTime) {
            this.activityTime = activityTime;
            return this;
        }

        public Builder setLevelOne(String levelOne) {
            this.levelOne = levelOne;
            return this;
        }

        public Builder setLevelTwo(String levelTwo) {
            this.levelTwo = levelTwo;
            return this;
        }

        public Builder setLevelThree(String levelThree) {
            this.levelThree = levelThree;
            return this;
        }

        public Builder newRequest(Request request) {
            this.activityId = request.activityId;
            this.activityName = request.activityName;
            this.activityTime = request.activityTime;
            //Parameter judgment can be added
            this.levelOne = request.levelOne;
            this.levelTwo = request.levelTwo;
            this.levelThree = request.levelThree;
            return this;
        }

        public Request build() {
            return new Request(this);
        }
    }

    public String activityId() {
        return activityId;
    }

    public String activityName() {
        return activityName;
    }

    public Integer activityTime() {
        return activityTime;
    }

    public String levelOne() {
        return levelOne;
    }

    public String levelTwo() {
        return levelTwo;
    }

    public String levelThree() {
        return levelThree;
    }

    @Override
    public String toString() {
        return "Request{" +
                "activityId='" + activityId + '\'' +
                ", activityName='" + activityName + '\'' +
                ", activityTime=" + activityTime +
                ", levelOne='" + levelOne + '\'' +
                ", levelTwo='" + levelTwo + '\'' +
                ", levelThree='" + levelThree + '\'' +
                '}';
    }
}

Define approval result object:

/**
 * Description: Authorization results
 * <br/>
 * AuthResult
 *
 * @author laiql
 * @date 2021/11/2 10:32
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthResult {
    private boolean isRatify;
    private String info;
}

Abstract processing request interface definition:

public interface Ratify {
    /**
     * Processing requests
     *
     * @param chain
     * @return
     */
    public AuthResult deal(Chain chain);

    /**
     * Interface Description: encapsulate request and Result for forwarding
     */
    interface Chain {
        /**
         * Get current request
         *
         * @return
         */
        Request request();

        /**
         * Forward request
         *
         * @param request
         * @return
         */
        AuthResult proceed(Request request);
    }
}

Realize the real wrapper Request and forwarding function class definition of Chain:

public class RealChain implements Ratify.Chain {

    /**
     * Specific Request instance
     */
    public Request request;
    /**
     * Ratify A collection of implementation classes for an interface
     */
    public List<Ratify> ratifyList;
    /**
     * Number of responsible persons who have processed the request
     */
    private Integer index;

    public RealChain(Request request, List<Ratify> ratifyList, Integer index) {
        this.request = request;
        this.ratifyList = ratifyList;
        this.index = index;
    }

    /**
     * Return the current Request object or the wrapped Request object
     *
     * @return
     */
    @Override
    public Request request() {
        return request;
    }

    /**
     * Specific forwarding function
     *
     * @param request
     * @return
     */
    @Override
    public AuthResult proceed(Request request) {
        AuthResult proceed = null;
        if (this.ratifyList.size() > this.index) {
            RealChain realChain = new RealChain(this.request, this.ratifyList, this.index + 1);
            Ratify ratify = this.ratifyList.get(this.index);
            proceed = ratify.deal(realChain);
        }
        return proceed;
    }
}

Define daily activity approval responsibility category:

@Slf4j
public class DailyAuthHandler implements Ratify {
    @Override
    public AuthResult deal(Chain chain) {
        Request request = chain.request();
        if (request.activityTime() > 1) {
            Request newRequest = new Request.Builder().newRequest(request).setLevelOne("1 Level approval - " + "Operators:[Zhang San] - Approval comments:[agree]").build();
            log.info("Active state:{}", newRequest.toString());
            //Submit to level II personnel for approval
            return chain.proceed(newRequest);
        }
        return new AuthResult(true, "Operators:[Zhang San] - Approval comments:[agree]");
    }
}

Define promotion activity approval responsibility class:

@Slf4j
public class PromotionAuthHandler implements Ratify {
    @Override
    public AuthResult deal(Ratify.Chain chain) {
        Request request = chain.request();
        if (request.activityTime() > 3) {
            Request newRequest = new Request.Builder().newRequest(request).setLevelTwo("2 Level approval - " + "Operations Manager:[Li Si] - Approval comments:[agree]").build();
            log.info("Active state:{}", newRequest.toString());
            //Submit to level 3 personnel for approval
            return chain.proceed(newRequest);
        }
        return new AuthResult(true, "Operations Manager:[Li Si] - Approval comments:[agree]");
    }
}

The approval responsibilities of the promotion activities are as follows:

@Slf4j
public class Activity618AuthHandler implements Ratify {
    @Override
    public AuthResult deal(Ratify.Chain chain) {
        Request request = chain.request();
        if (request.activityTime() <= 7) {
            Request buildRequest = new Request.Builder().newRequest(request).setLevelThree("3 Level approval - " + "Operations director:[Wang Wu] - Approval comments:[agree]").build();
            log.info("Active state:{}", buildRequest.toString());
        } else {
            log.info("Active state:{}", request.toString());
            return new AuthResult(false, "Approval failed! Activities over 7 days are not supported at present!");
        }
        return new AuthResult(true, "Operations director:[Wang Wu] - Approval comments:[agree]");
    }
}

Test case definition:

    @Test
    public void test() {
        //Tectonic activity
        Request request = new Request.Builder().setActivityId("0000000000001")
                .setActivityName("618 Big promotion")
                .setactivityTime(2).build();
        ChainOfResponsibilityClient chain = new ChainOfResponsibilityClient();
        AuthResult authResult = chain.execute(request);
        log.info("results of enforcement:{}", authResult);
    }

Expected results of implementation:

15:49:52.786 [main] INFO com.smartfrank.pattern.example.handler.DailyAuthHandler - Active state:Request{activityId='0000000000001', activityName='618 Big promotion', activityTime=6, levelOne='1 Level approval - Operators:[Zhang San] - Approval comments:[agree]', levelTwo='null', levelThree='null'}
15:49:52.790 [main] INFO com.smartfrank.pattern.example.handler.PromotionAuthHandler - Active state:Request{activityId='0000000000001', activityName='618 Big promotion', activityTime=6, levelOne='null', levelTwo='2 Level approval - Operations Manager:[Li Si] - Approval comments:[agree]', levelThree='null'}
15:49:52.790 [main] INFO com.smartfrank.pattern.example.handler.Activity618AuthHandler - Active state:Request{activityId='0000000000001', activityName='618 Big promotion', activityTime=6, levelOne='null', levelTwo='null', levelThree='3 Level approval - Operations director:[Wang Wu] - Approval comments:[agree]'}
15:49:52.790 [main] INFO com.smartfrank.pattern.ChainTest - results of enforcement:AuthResult(isRatify=true, info=Operations director:[Wang Wu] - Approval comments:[agree])

2.4 advantages and disadvantages

advantage:

  • Reduce the coupling degree and decouple the sender and receiver of the request.
  • The object is simplified so that the object does not need to know the structure of the chain.
  • It enhances the flexibility of assigning responsibilities to objects, and allows dynamic addition or deletion of responsibilities by changing the members in the chain or transferring their order.
  • It is convenient to add new request processing classes.
    Disadvantages:
  • There is no guarantee that the request will be received.
  • The system performance will be affected to some extent, and it is inconvenient to debug the code, which may cause circular calls.
  • It may not be easy to observe the characteristics of the runtime, which hinders debugging.

3, Summary

From the above scenario case code, we can realize that with the use of design patterns, the code will become more object-oriented and conform to the software design principles. At the same time, our code structure will become clear and clean.
The responsibility chain model handles the single responsibility and opening and closing principle well, simplifies the coupling, and makes the object relationship clearer. Moreover, the external caller does not need to care about how the responsibility chain is handled * (in the above program, the combination of responsibility chains can be packaged and provided to the outside) *. However, in addition to these advantages, it also needs to be used in appropriate scenarios to avoid confusion in performance and arrangement, and omission in debugging and testing.

Code address

Posted by anauj0101 on Tue, 02 Nov 2021 10:35:42 -0700