background

If A does not satisfy A condition, then return; if B does not satisfy A condition, then return. Five validations were written, and the following logic was executed after the validations passed, which was fine. After a while, the product raised requirements, similar to this method, I copied this method, but the verification conditions are slightly different, to become six verification.

At this time, I found that there were three problems. First, the code was repeated, and two copies of the same A condition, B condition and C condition were written without reuse. Second, “top-heavy”, for example, 100 lines of method, the first 60 lines are verification, the last 40 lines are really useful business code, when you look at a method function before verification is definitely not concerned with what the logic of the last 40 lines is, so shorten the number of lines of verification code. Third, the sequence, one method A is verified before B, the other method A is verified after B, it is very inconvenient to adjust.

At this time, I thought of using the chain of responsibility mode to optimize the solution.

define

In the Chain of Responsibility Pattern, each node in the Chain is regarded as an object, each node processes different requests, and the next node object is automatically maintained internally. When a request is sent from the head of the chain, it is passed along the chain path to each node object until an object processes the request. It belongs to the behavioral pattern.

The application scenario in life is the approval flow. The responsibility chain mode mainly decouples the request and processing. The customer only needs to send the request to the chain without caring about the specific content and processing details of the request. The request will be transmitted automatically until there is a node object to process it.

Generic UML class diagrams

example

Write a login validation examples of judgment, under general chain of responsibility pattern can match the model together with the builder, namely chain programming, interested can look at the boys builder pattern of great oaks grow from little acorns, because such chain looks more clear, and the traditional method is very abstract, it is hard to see somebody in front of the who, who somebody in the back of the As follows:

  AAAHandler.setNextHandler(deptManagerLeaveHandler);
  directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
  BBBHandler.setNextHandler(AAAHandler);
  deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);
Copy the code

This class has a next Handler Handler Handler, and a Builder. This class is used to build the chain, but it is also convenient for our chain programming.

public abstract class Handler<T> {

    protected Handler next;

    private void next(Handler next) {
        this.next = next;
    }

    public abstract void doHandler(Member member);

    public static class Builder<T> {
        private Handler<T> head;
        private Handler<T> tail;

        public Builder<T> addHandler(Handler handler) {
            if (this.head == null) {
                this.head = this.tail = handler;
                return this;
            }
            this.tail.next(handler);
            this.tail = handler;
            return this;
        }

        public Handler<T> build(a) {
            return this.head; }}}Copy the code

This ValidateHandler class checks whether the user name and password are null, returns if they are null, checks whether next is null, and passes them to the next handler.

public class ValidateHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (StringUtils.isEmpty(member.getUsername()) ||
                StringUtils.isEmpty(member.getPassword())) {
            System.out.println("User name and password cannot be empty");
            return;
        }
        if (null! = next) { next.doHandler(member); }}}Copy the code

Create a LoginHandler class to verify that the account password is correct

public class LoginHandler extends Handler {

    @Override
    public void doHandler(Member member) {
        if (!"jack".equals(member.getUsername()) || !"666".equals(member.getPassword())) {
            System.out.println("Incorrect username and password");
            return;
        }
        if (null! = next) { next.doHandler(member); }}}Copy the code

Create a permission validation AuthHandler class to determine whether a role has permissions

public class AuthHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (!"Administrator".equals(member.getRoleName())) {
            System.out.println("You're not an administrator, you don't have access.");
            return;
        }
        if (null! = next) { next.doHandler(member); }}}Copy the code

Create the execution business logic class

public class BusinessLogicHandler extends Handler {

    @Override
    public void doHandler(Member member) {
        System.out.println("Execute business logic..."); }}Copy the code

Well, let’s write a test class to test it

public class Test {

    public static void main(String[] args) {
        Handler.Builder builder = new Handler.Builder();
        // You can see who is first and who is last
        builder.addHandler(new ValidateHandler())
                .addHandler(new LoginHandler())
                .addHandler(new AuthHandler())
                .addHandler(new BusinessLogicHandler());
        Member member = new Member();
        member.setUsername("");
        member.setPassword(""); builder.build().doHandler(member); }}Copy the code

The user name and password cannot be empty

Change the user name and password

The user name and password are incorrect

Until the user name password permissions are set correctly

At this point all the validations have passed and the business logic is executed

Application in source code

Let’s look at a very common Filter class in the J2EE standard:

public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    public default void destroy(a) {}}Copy the code

The Filter interface is defined very simply and is equivalent to the handler abstract role in the chain of responsibility model. Let’s look at the MockFilterChain class implemented in Spring:

public class MockFilterChain implements FilterChain {

   @Nullable
   private ServletRequest request;
   @Nullable
   private ServletResponse response;
   private final List<Filter> filters;
   @Nullable
   private Iterator<Filter> iterator;


   public MockFilterChain(a) {
      this.filters = Collections.emptyList();
   }

   public MockFilterChain(Servlet servlet) {
      this.filters = initFilterList(servlet);
   }

   public MockFilterChain(Servlet servlet, Filter... filters) {
      Assert.notNull(filters, "filters cannot be null");
      Assert.noNullElements(filters, "filters cannot contain null values");
      this.filters = initFilterList(servlet, filters);
   }

   private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
      Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
      return Arrays.asList(allFilters);
   }

   @Nullable
   public ServletRequest getRequest(a) {
      return this.request;
   }

   @Nullable
   public ServletResponse getResponse(a) {
      return this.response;
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
      Assert.notNull(request, "Request must not be null");
      Assert.notNull(response, "Response must not be null");
      Assert.state(this.request == null."This FilterChain has already been called!");

      if (this.iterator == null) {
         this.iterator = this.filters.iterator();
      }
      // Core code execution
      if (this.iterator.hasNext()) {
         Filter nextFilter = this.iterator.next();
         nextFilter.doFilter(request, response, this);
      }

      this.request = request;
      this.response = response;
   }

   public void reset(a) {
      this.request = null;
      this.response = null;
      this.iterator = null; }... }Copy the code

Filters in List < Filter>. In the doFilter() method there is a core of code this.iterator.hasnext (). This is equivalent to executing the Filter method in the filters for loop. Although the writing is different, but also play the function of the chain of responsibility, so in the learning design patterns, not constrained in the standard way, many of them are variant, or laptop models, I write this is like the design patterns, and like the design patterns, this is very normal, can simplify the code, run efficiently is a good code.

The advantages and disadvantages

Advantages:

  1. Decouple requests from locations.
  2. The request handler (node object) only needs to focus on the request that he is interested in processing, for the request that is not interested, directly forward to the next level of node object.
  3. With chain transfer processing request function, request sender need not know the link structure, just wait for the request processing result.
  4. The link structure is flexible, and the responsibilities can be dynamically added or deleted by changing the link structure.
  5. Easy to extend new request processing classes (nodes), in accordance with the open closed principle.

Disadvantages:

  1. If the chain of responsibility is too long or the processing time is too long, the overall performance will be affected.
  2. If a node object has a circular reference, it will cause an infinite loop, causing the system to crash.

Source code for this article and other design patterns: github.com/xuhaoj/patt…