Java implements the responsibility chain pattern

Posted by webing on Tue, 18 Jan 2022 07:21:39 +0100

What is the responsibility chain design pattern

Responsibility chain design pattern is a common design pattern in software development. In this pattern, a request is processed step by step to form a chain. The request is processed in the nodes of the chain, and one node processes its own logic and then passes it to the next node.

In many open source libraries, you can see the use of the responsibility chain pattern, such as OkHttp, PermissionX, Filter in Spring, etc.

In daily development, such as login verification, return value processing and multiple pop-up boxes, the responsibility chain mode can be used. The following two cases are used to realize the responsibility chain mode.

Daily case

Requirement Description: the responsibility chain mode is used to implement the employee leave approval process. The team leader can approve leave less than 3 days, the manager can approve leave less than 10 days, and the boss can approve leave more than 10 days.

Analysis: using the object-oriented method, vacation is the business to be processed in the demand, and the team leader, manager and boss belong to the processing nodes in the responsibility chain.

When each node processes, if it meets the requirements, it will return the processing results. If it does not meet the requirements, it will go to the next node for processing.

1, Create holiday class Event

public class Event {

    // Leave days
    private int date;


    public int getDate() {
        return date;
    }

    public void setDate(int date) {
        this.date = date;
    }

}

2, Create a chain of responsibility

1. Create chain node abstract class

Each processing node object needs to inherit the abstract class and implement the proceed method. The execution logic is implemented in the proceed method. If the requirements are met, the execution returns. If the requirements are not met, it is handed over to the next chain node.

Use the constructor mode to add chain nodes. Refer to the article: This is the elegant use of the responsibility chain model

public abstract class CheckChain {

    // Current processing node
    protected CheckChain checker;

    // Set next processor
    public void setNextChecker(CheckChain checker) {
        this.checker = checker;
    }

    // Processing method, which each processor should implement
    public abstract void proceed(Event event);

    // Create using constructor mode
    public static class Builder {
        // Record the first processor and the next processor respectively, similar to the linked list structure
        private CheckChain head;
        private CheckChain tail;

        // Add processor
        public Builder addChecker(CheckChain chain) {
            if (this.head == null) {
                this.head = this.tail = chain;
                return this;
            }
            // Set next processor
            this.tail.setNextChecker(chain);
            this.tail = chain;
            return this;
        }

        public CheckChain build() {
            return this.head;
        }
    }
}

2. Create responsibility chain node

Create team leader, manager and boss processing nodes respectively.

Group leader chain node. The group leader can approve leave less than 3 days. If it exceeds 3 days, it needs to be approved by the manager.

public class GroupChain extends CheckChain {

     @Override
    public boolean proceed(Event event) {
        if (event.getDate() > 3) {
            System.out.println("The team leader's authority is not enough, and it is transferred to the superior leader for approval");
            if (checker != null) {
                return checker.proceed(event);
            }
            return false;
        } else {
            System.out.println("Approved by the team leader");
            return true;
        }
    }
}

In the manager chain node, managers can approve leave less than 10 days, and those more than 10 days need to be approved by the boss.

public class ManagerChain extends CheckChain {

     @Override
    public boolean proceed(Event event) {
        if (event.getDate() > 10) {
            System.out.println("The manager's authority is not enough, so it is transferred to the superior leader for approval");
            if (checker != null) {
                return checker.proceed(event);
            }
            return false;
        } else {
            System.out.println("Approved by manager");
            return true;
        }
    }
}

The boss can approve all holidays.

public class BossChain extends CheckChain {

     @Override
    public boolean proceed(Event event) {
        System.out.println("Approved by the boss");
        return true;
    }
}

3, Test procedure

// Create a leave slip for 30 days
Event event = new Event();
event.setDate(30);
// Create leader, manager and boss chain nodes
GroupChain groupChain = new GroupChain();
new CheckChain.Builder()
        .addChecker(groupChain)
        .addChecker(new ManagerChain())
        .addChecker(new BossChain())
        .build();
// The team leader initiates the operation
boolean result = groupChain.proceed(event);
System.out.println("Whether the leave is successful:" + result);

Execution results:

The team leader's authority is not enough, and it is transferred to the superior leader for approval
 The manager's authority is not enough, so it is transferred to the superior leader for approval
 Approved by the boss
 Whether the leave is successful: true

In this test procedure, for those who ask for leave, they do not focus on who has the authority to approve the leave, but on whether they finally ask for leave successfully. Therefore, the responsibility chain model only focuses on the results, not the process.

Handwritten OkHttp responsibility chain

The above implementation is only one of the implementation of the responsibility chain mode. The implementation of the responsibility chain in OkHttp is slightly different. In order to be more familiar with the responsibility chain mode, let's further understand the responsibility chain mode and OkHttp principle by writing the implementation mode of the responsibility chain in OkHttp.

Let's first look at a code that initiates a network request:

// Initialize OkHttpClient through constructor mode
OkClient client = new OkClient.Builder()
        .addInterceptor(new BridgeInterceptor())
        .addInterceptor(new NetworkInterceptor())
        .addInterceptor(new LogInterceptor())
        .addInterceptor(new CallServerInterceptor())
        .build();
// The request is created through the constructor pattern
Request request = new Request.Builder()
        .url("http:123.com")
        .build();
// Get the execution result after initiating the request
Response response = client.newCall(request).execute();
// Output execution results
System.out.println(response.toString());

The above code is a network request modeled on OkHttp. Let's see how the request process is implemented through the responsibility chain mode.

1, Create the OkClient class, which adds corresponding parameters through the constructor pattern

public class OkClient {

    // Create a collection to store interceptors
    private ArrayList<Interceptor> interceptors = new ArrayList<>();

    public OkClient(Builder builder) {
        this.interceptors = builder.arrayList;
    }

    // Returns the interceptor collection
    public List<Interceptor> interceptors() {
        return interceptors;
    }

    // Create originating request method
    public RealCall newCall(Request request) {
        return new RealCall(this, request);
    }

    public static class Builder {

        private ArrayList<Interceptor> arrayList = new ArrayList<>();

        public Builder addInterceptor(Interceptor interceptor) {
            arrayList.add(interceptor);
            return this;
        }

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

2, Create responsibility chain interface

OkHttp uses interfaces, and the previous case used abstract classes.

1. Interceptor interface and internal Chain interface

public interface Interceptor {

    // Intercept processing response
    Response intercept(Chain chain);

    // The OkHttp responsibility chain processes not only Request requests but also Response requests
    interface Chain {

        Request request();

        Response proceed(Request request);
    }
}

3, Create Request class and Response class

1. Request class
The Request class is created through the constructor mode, as shown below. The Request class will pass the Request url, Request type, Request header and Request body, and will check and set these values in the interceptor.

public class Request {

    private String url;
    private String mediaType;
    private String body;
    private String header;

    public Request(Builder builder) {
        this.url = builder.url;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getMediaType() {
        return mediaType;
    }

    public void setMediaType(String mediaType) {
        this.mediaType = mediaType;
    }

    public static class Builder {

        private String url;

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

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

2. Response class
The Response class is relatively simple and returns the required print information, as shown below:

public class Response {

    private String bridgeInterceptor;
    private String logInterceptor;
    private String networkInterceptor;
    private String callServerInterceptor;
    // Omit some codes
}

4, Create interceptor

The Interceptor created needs to implement the Interceptor interface, and create BridgeInterceptor, NetworkInterceptor, LogInterceptor and CallServerInterceptor respectively.

1. Create BridgeInterceptor class

public class BridgeInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        // Processing requests
        Request request = chain.request();
        String mediaType = request.getMediaType();
        // Set MediaType in this interceptor
        if (TextUtils.isEmpty(mediaType)) {
            request.setMediaType("BridgeInterceptor");
        }
        // Processing response results
        Response response = chain.proceed(request);
        // Set values for response results
        if (TextUtils.isEmpty(response.getBridgeInterceptor())) {
            response.setBridgeInterceptor("BridgeInterceptor");
        }
        return response;
    }
}

2. Create NetworkInterceptor class

public class NetworkInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        // Processing requests
        Request request = chain.request();
        // Set the request header in the interceptor
        if (TextUtils.isEmpty(request.getHeader())) {
            request.setHeader("RetryInterceptor");
        }
        // Processing response results
        Response response = chain.proceed(request);
        // Set values for response results
        if (TextUtils.isEmpty(response.getNetworkInterceptor())) {
            response.setNetworkInterceptor("NetworkInterceptor");
        }
        return response;
    }
}

3. Create LogInterceptor class

public class LogInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        Request request = chain.request();
        // Set the request body in the interceptor
        if (TextUtils.isEmpty(request.getBody())) {
            request.setBody("LogInterceptor");
        }
        // Processing response results
        Response response = chain.proceed(request);
        // Set values for response results
        if (TextUtils.isEmpty(response.getLogInterceptor())) {
            response.setLogInterceptor("LogInterceptor");
        }
        return response;
    }
}

4. Create CallServerInterceptor class

The CallServerInterceptor interceptor belongs to the last interceptor in OkHttp. It will no longer execute the processed method and will directly return the Response

public class CallServerInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        Request request = chain.request();
        System.out.println("CallServerInterceptor: " + request.toString());
        // If the request header, request type and request body are not empty, a network request is initiated and the result is returned
        if (!TextUtils.isEmpty(request.getHeader()) && !TextUtils.isEmpty(request.getMediaType())
                && !TextUtils.isEmpty(request.getBody())) {
            // Initiate a network request and return the result
            Response response = new Response();
            response.setCallServerInterceptor("CallServerInterceptor");
            return response;
        }
        return null;
    }
}

5, Create RealCall class

In OkHttp, the calling process is OkHttpClient.. Newcall() -- > create RealCall – > execute RealCall Execute() method -- > RealCall Getresponsewithinterceptorchain() method, add an interceptor in this method and call the proceed method of the first interceptor;

public Response getResponseWithInterceptorChain() {
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, 0, this.request);
    return chain.proceed(request);
}

6, Create RealInterceptorChain class

In the processed method of the RealInterceptorChain class, the call of the responsibility chain is started through the index index.

public class RealInterceptorChain implements Interceptor.Chain {

    private int index;
    private List<Interceptor> interceptors;
    private Request request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, Request request) {
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public Request request() {
        return this.request;
    }

    @Override
    public Response proceed(Request request) {
        if (index >= interceptors.size()) throw new Error("The current index and interceptor length do not match");
        // Get the next responsibility chain interceptor and index+1
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index + 1, request);
        // Get current interceptor
        Interceptor interceptor = interceptors.get(index);
        // Execute the interception method and pass the next interceptor
        Response response = interceptor.intercept(next);
        return response;
    }
}

7, Final output

// Print out the request parameters added during the request process
CallServerInterceptor: Request{url='http:123.com', mediaType='BridgeInterceptor', body='LogInterceptor', header='RetryInterceptor'}
// There are 4 interceptor results in the returned Response
Response{bridgeInterceptor='BridgeInterceptor', logInterceptor='LogInterceptor', networkInterceptor='NetworkInterceptor', callServerInterceptor='CallServerInterceptor'}

The responsibility chain mode in OkHttp has been implemented. It can be seen that the implementation in OkHttp is also very elegant. Through this article, you can not only learn the principle of responsibility chain mode, but also be familiar with the function principle of interceptors in the request and return process of OkHttp.

The above source code can view the project 034-android-chain

Love official account.

Topics: Java Algorithm