Strategy mode and responsibility chain mode

1, Strategy mode

Strategy Pattern, also known as policy pattern, encapsulates the defined algorithm families and so that they can be replaced with each other, so that the change of the algorithm will not affect the users using the algorithm. It belongs to behavioral mode.

Define a family of algor ithms, encapsulate each one, and make them inter changeable

**Strategy patterns are also widely used in life scenes** For example, a person's tax rate is related to his salary, and different salary levels correspond to different tax rates. For another example, in the context of Internet mobile payment, we need to choose the payment method after each order and before payment.

The policy pattern can solve the complexity and complexity caused by using if... else or switch... case when there are many similar algorithms
Swollen. In daily business development, the policy mode is applicable to the following scenarios:
1. There are many ways to deal with the same - type problems, and each can solve the problem independently;
2. Scenarios where the algorithm needs to switch freely;
3. Scenarios that need to mask algorithm rules.
First, let's look at the general UML class diagram of the policy pattern:

From the class diagram, we can see that the delegation mode has three participating roles:

Abstract Task: defines an abstract interface with several implementation classes.

Delegate: it is responsible for making decisions among specific role instances, judging and calling specific implementation methods.

Concrete is the role that really performs the task.

Note: the function of context in the policy mode is to isolate the coupling between the client and the policy class, so that the client can fully communicate with the context without specific policies.

2, Policy mode instance

There are many possibilities for preferential strategies, such as receiving coupon deduction, cash back promotion and group discount. Let's simulate it with code,
First, we create an abstraction of the promotion strategy
PromotionStrategy :

/**
 * Promotion strategy abstraction 
 */
public interface IPromotionStrategy {
    void doPromotion();
}

Then create CouponStrategy, cashback promotion strategy, GroupbuyStrategy and EmptyStrategy respectively:

public class CouponStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("Use coupon deduction");
    }
}

CashbackStrategy class:

public class CashbackStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("Cash back, directly to Alipay account.");
    }
}

GroupbuyStrategy class:

public class GroupbuyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("5 A group of people can enjoy a discount");
    }
}

EmptyStrategy class:

public class EmptyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("No discount");
    }
}

Then create the promotion activity scheme PromotionActivity class:

public class PromotionActivity {
    private IPromotionStrategy strategy;

    public PromotionActivity(IPromotionStrategy strategy) {
        this.strategy = strategy;
    }

    public void execute(){
        strategy.doPromotion();
    }
}

Write client test class:

public class Test {
    public static void main(String[] args) {
        PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
        PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
        activity618.execute();
        activityllll.execute();
    }
}

The operation effect is as follows:

Use coupon deduction
 Cash back, directly to Alipay account.

At this time, the partners will find that it is not practical to put the above trial code into the actual business scenario. Because when we do activities, we often make dynamic selection of promotion strategies according to different needs, and will not implement a variety of discounts at one time. So, I
Your code usually says this:

public class Test {
    public static void main(String[] args) {
        PromotionActivity promotionActivity = null;
        String promotionKey = "COUPON";
        if (StringUtils.equals(promotionKey, "COUPON")) {
            promotionActivity = new PromotionActivity(new CouponStrategy());
        } else if (StringUtils.equals(promotionKey, "CASHBACK")) {
            promotionActivity = new PromotionActivity(new CashbackStrategy());
        }
        promotionActivity.execute();
    }
}

After such transformation, the business needs are met, and customers can choose different preferential strategies according to their own needs. But after a while
With the business accumulation of time, our promotional activities will be more and more. Qian yes, our little brother of the program ape is too busy. We have to change the code all night before every activity, and we have to do repeated tests. The judgment logic may become more and more complex. At this time, we don't need to think
Should the code be refactored? Reviewing the design patterns we learned before, how should we optimize this code? Actually, we can get married
Combined single example mode and factory pendulum. Create the PromotionStrategyFactory class:

public class PromotionStrategyFacory {

    private static final IPromotionStrategy EMPTY = new EmptyStrategy();
    private static Map<String, IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();

    static {
        PROMOTIONS.put(PromotionKey.COUPON, new CouponStrategy());
        PROMOTIONS.put(PromotionKey.CASHBACK, new CashbackStrategy());
        PROMOTIONS.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
    }

    private PromotionStrategyFacory() {
    }

    public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
        IPromotionStrategy strategy = PROMOTIONS.get(promotionKey);
        return strategy == null ? EMPTY : strategy;
    }

    public static Set<String> getPromotionKeys() {
        return PROMOTIONS.keySet();
    }

    private interface PromotionKey {
        String COUPON = "COUPON";
        String CASHBACK = "CASHBACK";
        String GROUPBUY = "GROUPBUY";
    }
}

At this time, our client code should be written like this

public class Test {
    public static void main(String[] args) {
        PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
        PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
        activity618.execute();
        activityllll.execute();
    }
}

The operation effect is as follows:

Use coupon deduction

After code optimization, is the maintenance of our program ape brother easier? Each new activity does not affect the original code
Logic.

Implement the business scenario of selecting payment method with policy mode
In order to deepen our understanding of the strategic model, let's take another case. I believe that buddies have used Alipay, WeChat payment and UnionPay.
Payment and Jingdong IOUs. - A common application scenario is that when you place an order for payment, you will be prompted to select the payment method. If the user does not select it,
The system will also default to the recommended payment method for settlement. Let's take a look at the class diagram. Let's use the policy pattern to describe this business field:

Create the Payment abstract class, define the Payment specification and Payment logic, and the code is as follows:

public abstract class Payment {

    public abstract String getName();

    //General logic is implemented in abstract classes
    public MsgResult pay(String uid, double amount) {
        //Is the balance sufficient
        if (queryBalance(uid) < amount) {
            return new MsgResult(500, "Payment failed", "Sorry, your credit is running low");
        }
        return new MsgResult(200, "Payment successful", "Payment amount" + amount);
    }

    protected abstract double queryBalance(String uid);
}

Create specific payment methods respectively, Alipay AliPay class:

public class AliPay extends Payment {
    public String getName() {
        return "Alipay";
    }

    protected double queryBalance(String uid) {
        return 900;
    }
}

JD Baitiao JDPay class:

public class JDPay extends Payment {
    public String getName() {
        return "Jingdong Baitiao";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}


Wechat payment WechatPay class:

public class WechatPay extends Payment {
    public String getName() {
        return "Wechat payment";
    }

    protected double queryBalance(String uid) {
        return 263;
    }
}

UnionPay payment:

public class UnionPay extends Payment {
    public String getName() {
        return "UnionPay payment";
    }

    protected double queryBalance(String uid) {
        return 120;
    }
}

Create packaging class MsgResult for payment status:

public class MsgResult {
    private int code;
    private Object data;
    private String msg;

    public MsgResult(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MsgResult{" +
                "code=" + code +
                ", data=" + data +
                ", msg='" + msg + '\'' +
                '}';
    }
}

Create payment policy management class:

public class PayStrategy {
    public static  final String ALI_PAY = "AliPay";
    public static  final String JD_PAY = "JdPay";
    public static  final String WECHAT_PAY = "WechatPay";
    public static  final String UNION_PAY = "UnionPay";
    public static  final String DEFAULT_PAY = ALI_PAY;

    private static Map<String,Payment> strategy = new HashMap<String,Payment>();

    static {
        strategy.put(ALI_PAY,new AliPay());
        strategy.put(JD_PAY,new JDPay());
        strategy.put(WECHAT_PAY,new WechatPay());
        strategy.put(UNION_PAY,new UnionPay());
    }

    public static Payment get(String payKey){
        if(!strategy.containsKey(payKey)){
            return strategy.get(DEFAULT_PAY);
        }
        return strategy.get(payKey);
    }
}

Create Order class:

public class Order {
    private String uid;
    private String orderId;
    private double amount;

    public Order(String uid, String orderId, double amount) {
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }

    public MsgResult pay(){
        return pay(PayStrategy.DEFAULT_PAY);
    }

    public MsgResult pay(String payKey){
        Payment payment = PayStrategy.get(payKey);
        System.out.println("Welcome" + payment.getName());
        System.out.println("The transaction amount is" + amount + ",Start deduction");
        return payment.pay(uid,amount);
    }
}

Test code:

public class Test {
    public static void main(String[] args) {
        Order order = new Order("1","2020031401000323",324.5);
        System.out.println(order.pay(PayStrategy.UNION_PAY));
    }
}

Operation results:

Welcome to UnionPay payment
 The transaction amount is 324.5,Start deduction
MsgResult{code=500, data=Sorry, your credit is running low, msg='Payment failed'}

We hope that through the familiar business scenarios, we can give examples to let our partners have a deeper understanding of the strategic model. I hope my friends are here
Trial and work reflect their own advantages.

3, Application of policy pattern in source code

The embodiment of policy mode in the framework source code:
First, let's look at a commonly used Comparator interface in JDK. We see a commonly used compare()
Method is a policy abstract implementation:

public interface Comparator<T> {
	int compare(T o1, T o2);
}

There are many implementation classes under Comparator abstraction. We often pass Comparator as a parameter as a sorting strategy,
For example, the parallelSort method of the Arrays class:

public class Arrays {
    public static void parallelSort(byte[] a) {
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1);
        else
            new ArraysParallelSortHelpers.FJByte.Sorter
            (null, a, new byte[n], 0, n, 0,
             ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
             MIN_ARRAY_SORT_GRAN : g).invoke();
    }
}

And the construction method of TreeMap:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
}

This is the application of Comparator in JDK source code. Let's look at the application of policy pattern in Spring source code
Resource class:

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return true;
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}

Although we do not directly use the Resource class, we often use its subclasses, such as:

/*
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see ClassPathResource
 * @see FileSystemResource
 * @see PathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */

4, Advantages and disadvantages of policy mode

advantage:

1. The strategy mode conforms to the opening and closing principle.

2. Avoid using multiple conditional transition statements, such as if... else... Statements and switch statements

3. Using policy mode can improve the confidentiality and security of the algorithm.

Disadvantages:

1. The client must know all the policies and decide which policy class to use.

2. Many policy classes will be generated in the code, which increases the difficulty of maintenance.

5, Responsibility chain model

The chain of responsibility pattern treats each node in the chain as an object, and each node handles
The clearing is different, and the internal automatic maintenance - next - node object. When a request is sent from the head end of the chain, it will follow the path of the chain
Pass it to each node object in turn until an object handles the request. It belongs to thousands of behavioral patterns.

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to hand I e the request. Chain the receiving objects and pass the request along the chain until an object handles it
Explanation: multiple objects have the opportunity to process the request, thus avoiding 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 handles it.

6, Application scenario of responsibility chain model

In daily life, the responsibility chain is quite common. We usually deal with some affairs through the cooperation of various departments
Complete a task. Each department has its own responsibilities. Therefore, many times when things are half done, they will be handed over to the next department,
It can't be completed until all departments have finished fishing. In addition, as we usually say, passing five passes and cutting six generals is actually a chain of responsibility.

The responsibility chain mode mainly decouples the request and processing. Customers only need to send the clear request to the chain without caring about the specific content of the request
And processing details, the request will be automatically passed until a node object is processed.

Applicable to the following application scenarios:

1. Multiple objects can process the same request, but the specific object to be processed is dynamically determined at run time;

2. Submit a request to one of multiple objects without specifying the receiver;

3. You can dynamically specify a set of objects to handle requests.


From the UML class diagram, we can see that the responsibility chain pattern mainly includes two roles:

Abstract Handler: defines a method of request processing and maintains the Handler object of the next processing node
quote;

Concrete handler: process the request and forward it if it is not interested.

The essence of responsibility chain mode is to decouple request and processing, so that requests can be transmitted and processed in the processing chain; Understanding the responsibility chain model should
When understanding its mode (TAO) rather than its specific implementation (Art), the uniqueness of the responsibility chain mode is that it combines node processors into
The chain structure allows the node itself to decide whether to process or forward the request, which is equivalent to making the request flow.

Data verification and interception using responsibility chain mode
First, create an entity class Member:

@Data
public class Member {
    private String loginName;
    private String loginPass;
    private String roleName;

    public Member(String loginName, String loginPass) {
        this.loginName = loginName;
        this.loginPass = loginPass;
    }
}

Then let's look at a piece of code we often write:

public class MemberService {
    public static void main(String[] args) {
        MemberService service = new MemberService();
        service.login("tom", "666");
    }

    public void login(String loginName, String loginPass) {
        if (StringUtils.isEmpty(loginName) ||
                StringUtils.isEmpty(loginPass)) {
            System.out.println("User name and password are empty");
            return;
        }
        System.out.println("The user name and password cannot be empty. You can proceed");
        Member member = checkExists(loginName, loginPass);
        if (null == member) {
            System.out.println("user does not exist");
            return;
        }
        System.out.println("Login succeeded!");
        if (!"administrators".equals(member.getRoleName())) {
            System.out.println("You are not an administrator and do not have operation permission");
            return;
        }
        System.out.println("Allow operation");
    }

    private Member checkExists(String loginName, String loginPass) {
        Member member = new Member(loginName, loginPass);
        member.setRoleName("administrators");
        return member;
    }
}

Excuse me, my friends, do you often do this? In the above code, the main function is to verify the data before login, but
after
, judgment logic is sequential. First, make a non empty judgment, then check whether the account is valid, and finally obtain the user role. Then root
Whether there is operation permission is matched according to the permission of the user role. So such verifiable code is generally essential
However, the code written in the specific business code is very bloated, so we can use the responsibility chain model
, connect these inspection steps in series, and
It does not affect the beauty of the code. It can enable us to focus more on a specific business logic processing when coding.

Let's use the responsibility chain pattern to optimize the code. First, create a Handler class:

public abstract class Handler {
    protected Handler next;
    public void next(Handler next){ this.next = next;}
    public abstract void doHandler(Member member);
}

We create a non null validation ValidateHandler class, a login validation LoginHandler class, and a permission validation AuthHandler class
Class, look at the code ValidateHandler class:

public class ValidateHandler extends Handler {
    public void doHandler(Member member) {
        if (StringUtils.isEmpty(member.getLoginName()) ||
                StringUtils.isEmpty(member.getLoginPass())) {
            System.out.println("User name and password are empty");
            return;
        }
        System.out.println("The user name and password cannot be empty. You can proceed");
        next.doHandler(member);
    }
}

LoginHandler class:

public class LoginHandler extends Handler {
    public void doHandler(Member member) {
        System.out.println("Login succeeded!");
        member.setRoleName("administrators");
        next.doHandler(member);
    }
}

AuthHandler class:

public class AuthHandler extends Handler {
    public void doHandler(Member member) {
        if (!"administrators".equals(member.getRoleName())) {
            System.out.println("You are not an administrator and do not have operation permission");
            return;
        }
        System.out.println("Allow operation");
    }
}

Next, modify the code in the Memberservice. In fact, we only need to connect the previously defined handlers according to the business requirements to form a chain

public class MemberService {

    public void login(String loginName, String loginPass) {
        Handler validateHandler = new ValidateHandler();
        Handler loginHandler = new LoginHandler();
        Handler authHandler = new AuthHandler();
        validateHandler.next(loginHandler);
        loginHandler.next(authHandler);
        validateHandler.doHandler(new Member(loginName, loginPass));
    }
}

Look at the client call code:

public class Test {
    public static void main(String[] args) {
        MemberService memberService = new MemberService();
        memberService.login("tom","666");
    }
}

The operation results are as follows:

The user name and password are verified successfully. You can proceed
 Login succeeded!
You are an administrator and are not allowed to operate

In fact, many of the permission verification frameworks we usually use use this principle to decouple the permission processing of various dimensions before verification
In series, they only deal with their relevant responsibilities. If the responsibility is not related to yourself, it will be thrown to the next Handler in the chain, commonly known as kicking
Ball.

The responsibility chain model is combined with the builder model
Because the responsibility chain model has a chain structure, and in the above code, we see that the role responsible for assembling the chain structure is
MemberService. When the chain structure is long, the work of MemberService will be very cumbersome, and the MemberService generation
The code is relatively bloated, and subsequent changes to the handler or message type must be modified in MemberService, which does not comply with the opening and closing principle. The reason for these problems is that the assembly of chain structure is too complex, and it is natural for us to create complex structure
Think of the builder mode. Using the builder mode, we can automatically chain assemble the processing node objects specified by MemberService. The customer only needs to specify the processing node objects, and there is no need to care about anything else, and the processing node objects specified by the customer are not in the same order
At the same time, the chain structure is also different. Let's modify the Handler code:

public abstract class Handler<T> {
    protected Handler next;
    public 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){
           // do {
                if (this.head == null) {
                    this.head = this.tail = handler;
                    return this;
                }
                this.tail.next(handler);
                this.tail = handler;
           // }while (false);// In the real framework, if it is a two-way linked list, it will judge whether it has reached the tail
            return this;
        }

        public Handler<T> build(){
            return this.head;
        }
    }
}

Then, modify the code of MemberService:

public class MemberService {
    public void login(String loginName,String loginPass){
        Handler.Builder builder = new Handler.Builder();
        builder.addHandler(new ValidateHandler())
               .addHandler(new LoginHandler())
               .addHandler(new AuthHandler());
        builder.build().doHandler(new Member(loginName,loginPass));
        //Someone who has used Netty must have seen it
    }
}

Because the Builder mode wants to build a node Handler, we take Builder as the static internal class of Handler, and because the client does not need chain assembly, we can also use the chain assembly method next. Method is set to private so that the Handler
Higher cohesion, code as follows:

public abstract class Handler<T> {
    protected Handler chain;

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

Through this case, the partners should have felt the fine ITo of the combination of responsibility chain and builder

7, The embodiment of responsibility chain model in source code

First, let's take a look at the application of the responsibility chain pattern in JDK and a very common Filter class in J2EE standard:

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;

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

    public void destroy();
}

The definition of this Filter interface is very simple, which is equivalent to the abstract role of Handler in the responsibility chain model. How does it form a responsibility chain? Let me look at another class. In fact, in the last parameter of the dofilter () method, we have seen that its type is
FilterChain class, see its source code:

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}

There is only one doFilter() method defined in the FilterChain class, so how are they connected in series into a responsibility chain? In fact, J2EE only defines a specification, and the specific processing logic is implemented by the user himself. Let's look at an implementation of Spring
MockFilterChain class:

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() {
      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() {
      return this.request;
   }

   @Nullable
   public ServletResponse getResponse() {
      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();
      }

      if (this.iterator.hasNext()) {
         Filter nextFilter = this.iterator.next();
         nextFilter.doFilter(request, response, this);
      }

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

   public void reset() {
      this.request = null;
      this.response = null;
      this.iterator = null;
   }

   private static class ServletFilterProxy implements Filter {
      private final Servlet delegateServlet;

      private ServletFilterProxy(Servlet servlet) {
         Assert.notNull(servlet, "servlet cannot be null");
         this.delegateServlet = servlet;
      }

      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         this.delegateServlet.service(request, response);
      }

      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
      }

      @Override
      public void destroy() {
      }

      @Override
      public String toString() {
         return this.delegateServlet.toString();
      }
   }
}

It puts all the filters in the chain into the List, and then calls doFilter. Method iterates over the List, that is, List
The filters in are executed sequentially.

Let's take another look at the classic serialization processing in Netty. Pipeline adopts the responsibility chain design mode. Its bottom layer adopts the data structure of two-way linked list to connect the processors on the chain in series. With the arrival of each request from the client, Netty believes that all processors in pipeline have the opportunity to process it. Therefore, all requests to be stacked are propagated from the beginning node to the end node
The news will not be released until. Take a look at nety's responsibility processor interface ChannelHandler:

public interface ChannelHandler {
    // Called when the handler is added to the real context and ready to handle the event
	// handler is added to the callback 
    void handlerAdded(ChannelHandlerContext var1) throws Exception;

    // It is the callback after the handler is removed
    void handlerRemoved(ChannelHandlerContext var1) throws Exception;

    /** @deprecated */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;

    @Inherited
    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sharable {
    }
}

Netty makes a more fine-grained division of responsibility processing interfaces. Processors are divided into two types: one is stack processor
Channelnboundhandler, and the other is the out of stack processor ChannelOutboundHandler. Both interfaces inherit from
ChannelHandler. And all processors are finally added to Pipeline. Therefore, add or delete the interface of the responsible processor
The behavior is specified in Netty's Channelpipeline:

public interface ChannelPipeline extends ChannelInboundInvoker,ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {

    ChannelPipeline addFirst(String name, ChannelHandler handler);

    ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addLast(String name, ChannelHandler handler);

    ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
    ...
}

In the default implementation class, all handlers are concatenated into a linked list:

public class DefaultChannelPipeline implements ChannelPipeline {

    static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);

    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    ...
}

At any node in the Pipeline, as long as we do not manually propagate down, this event will terminate propagation at the current node. For the stack data, it will be passed to the tail node for recycling by default. If we do not carry out the next propagation, the event will terminate at the current time
Node. For the out of stack data, writing the data to the client also means watching the termination of the event.

Of course, the chain of responsibility pattern is also widely used in many security frameworks, such as Spring Security. Apache Shiro
For the responsibility chain mode in the design mode, interested partners can try to study it by themselves.

No matter how it is implemented in most frameworks, all implementations are similar. In fact, if we look at it from the perspective of designers
The source code is very beneficial for us to learn the source code and coding skills, because in this way, we can learn from a higher perspective
Learn good ideas instead of drilling into a code detail. We need to have a macro concept for all design, so learn
It's easier to learn.

8, Advantages and disadvantages of responsibility chain model

advantage:

1. Decouple requests from processing;

2. The request handler (node object) only needs to pay attention to the requests they are interested in and process them. For the requests they are not interested in, they can be transferred directly
Send to the next level node object;

3. It has the function of chain transmission and request processing. The request sender does not need to know the link structure, but only needs to wait for the request processing result;

4. The link structure is flexible, and responsibilities can be dynamically added or deleted by changing the link structure;
5. It is easy to extend the new request processing class (node) and conforms to the opening and closing principle.

Disadvantages:

1. If the responsibility chain is too long or the processing time is too long, the overall performance will be affected

2. If there is a circular reference to the node object, it will cause an endless loop and lead to system crash;

References: strategy model and reference model

Keywords: Java Algorithm

Added by dansk on Sun, 19 Sep 2021 17:09:23 +0300