Javascript chain of responsibility pattern

Chain of Responsibility Patterns in Reality

Examples of the Chain of Responsibility pattern are not difficult to find in reality. The following are two common scenarios related to the Chain of Responsibility pattern.

❏ If you can squeeze into the bus smoothly in the morning rush hour, then it is estimated that you will have a very happy day. Because there are too many people on the bus, I often can't find the conductor after getting on the bus, so I have to pass the two yuan coin to the front. Unless you are lucky enough that the first person standing in front of you is the conductor, your coin usually has to be passed through N hands before it finally reaches the conductor.

❏ If you can squeeze into the bus smoothly in the morning rush hour, then it is estimated that you will have a very happy day. Because there are too many people on the bus, I often can't find the conductor after getting on the bus, so I have to pass the two yuan coin to the front. Unless you are lucky enough that the first person standing in front of you is the conductor, your coin usually has to be passed through N hands before it finally reaches the conductor.

Take order coupons as an example:

Suppose we are responsible for an e-commerce website that sells mobile phones. After two rounds of reservations with a deposit of 500 yuan and a deposit of 200 yuan (the order has been generated at this time), we have now reached the stage of formal purchase.

The company has certain preferential policies for users who have paid a deposit. After the official purchase, users who have paid a deposit of 500 yuan will receive a coupon of 100 yuan in the mall, users with a deposit of 200 yuan will receive a coupon of 50 yuan, and users who have not paid a deposit before can only enter the normal purchase mode , that is, there are no coupons, and there is no guarantee that you can buy it when the stock is limited.

Our order page is a template that PHP spits out, and at the beginning of page load, PHP passes a few fields to the page.
❏ orderType: Indicates the order type (deposit user or ordinary purchaser). When the code value is 1, it is a 500 yuan deposit user, when it is 2, it is a 200 yuan deposit user, and when it is 3, it is an ordinary purchaser.

❏ pay: indicates whether the user has paid the deposit, the value is true or false, although the user has placed an order with a deposit of 500 yuan, if he has not paid the deposit, he can only be downgraded to the normal purchase mode now.

❏ stock: Indicates the current inventory of mobile phones used for ordinary purchases. Users who have paid a deposit of 500 yuan or 200 yuan are not subject to this restriction.

no responsibility model

If you use the chain of responsibility pattern, you can write it like this:

        var order = function( orderType, pay, stock ){
    
    
            if ( orderType === 1 ){
    
            // 500元定金购买模式
              if ( pay === true ){
    
        // 已支付定金
                  console.log( '500元定金预购,得到100优惠券’ );
              }else{
    
        // 未支付定金,降级到普通购买模式
                  if ( stock > 0 ){
    
        // 用于普通购买的手机还有库存
                      console.log( ’普通购买,无优惠券’ );
                  }else{
    
    
                      console.log( '手机库存不足' );
                  }
              }
            }

            else if ( orderType === 2 ){
    
         // 200元定金购买模式
              if ( pay === true ){
    
    
                  console.log( '200元定金预购, 得到50优惠券' );
              }else{
    
    
                  if ( stock > 0 ){
    
    
                      console.log( '普通购买, 无优惠券' );
                  }else{
    
    
                      console.log( '手机库存不足' );
                  }
              }
            }

            else if ( orderType === 3 ){
    
    
              if ( stock > 0 ){
    
    
                  console.log( '普通购买, 无优惠券' );
              }else{
    
    
                  console.log( '手机库存不足' );
              }
            }
        };

        order( 1 , true, 500);  // 输出: 500元定金预购, 得到100优惠券

Although the current project is running normally, the next maintenance work is undoubtedly a nightmare.

Refactor your code with the Chain of Responsibility pattern

First, you need to rewrite the node functions that represent the three purchase modes. We agree that if a node cannot process the request, it will return a specific string 'nextSuccessor' to indicate that the request needs to be passed on:(Currently you can handle this via booleans)

        var order500 = function( orderType, pay, stock ){
    
    
            if ( orderType === 1 && pay === true ){
    
    
              console.log( '500元定金预购,得到100优惠券’ );
            }else{
    
    
              return 'nextSuccessor';    // 我不知道下一个节点是谁,反正把请求往后面传递
            }
        };

        var order200 = function( orderType, pay, stock ){
    
    
            if ( orderType === 2 && pay === true ){
    
    
              console.log( '200元定金预购,得到50优惠券’ );
            }else{
    
    
              return 'nextSuccessor';    // 我不知道下一个节点是谁,反正把请求往后面传递
            }
        };

        var orderNormal = function( orderType, pay, stock ){
    
    
            if ( stock > 0 ){
    
    
              console.log( ’普通购买,无优惠券’ );
            }else{
    
    
              console.log( ’手机库存不足’ );
            }
        };

Next, we need to wrap the function into the responsibility chain node. We define a constructor Chain. The parameter passed in the new Chain is the function that needs to be wrapped. At the same time, it also has an instance attribute this.successor, which represents the function in the chain. next node. In addition, there are two functions in the prototype of Chain, and their functions are as follows:

        // Chain.prototype.setNextSuccessor  指定在链中的下一个节点
        // Chain.prototype.passRequest  传递请求给某个节点

        var Chain = function( fn ){
    
    
            this.fn = fn;
            this.successor = null;
        };

        Chain.prototype.setNextSuccessor = function( successor ){
    
    
            return this.successor = successor;
        };

        Chain.prototype.passRequest = function(){
    
    
            var ret = this.fn.apply( this, arguments );

            if ( ret === 'nextSuccessor' ){
    
    
                return this.successor && this.successor.passRequest.apply( this.successor, arguments );
            }

            return ret;
        };

Now we package the three order functions as nodes of the chain of responsibility:

        var chainOrder500 = new Chain( order500 );
        var chainOrder200 = new Chain( order200 );
        var chainOrderNormal = new Chain( orderNormal );

Then specify the order of the nodes in the chain of responsibility:

        chainOrder500.setNextSuccessor( chainOrder200 );
        chainOrder200.setNextSuccessor( chainOrderNormal );

Finally pass the request to the first node:

        chainOrder500.passRequest( 1, true, 500 );    // 输出:500元定金预购,得到100优惠券
        chainOrder500.passRequest( 2, true, 500 );    // 输出:200元定金预购,得到50优惠券
        chainOrder500.passRequest( 3, true, 500 );    // 输出:普通购买,无优惠券
        chainOrder500.passRequest( 1, false, 0 );     // 输出:手机库存不足

Through improvement, we can freely and flexibly add, remove, and modify the order of nodes in the chain. If one day the website operator comes up with a 300 yuan deposit to support the purchase, then we can add a node in the chain:

        var order300 = function(){
    
    
            // 具体实现略
        };

        chainOrder300= new Chain( order300 );
        chainOrder500.setNextSuccessor( chainOrder300);
        chainOrder300.setNextSuccessor( chainOrder200);

asynchronous chain of responsibility

We let each node function synchronously return a specific value "nextSuccessor" to indicate whether to pass the request to the next node. In real development, we often encounter some asynchronous problems. For example, we need to initiate an ajax asynchronous request in the node function, and the result returned by the asynchronous request can determine whether to continue to passRequest in the chain of responsibility.

At this time, it is meaningless for the node function to return "nextSuccessor" synchronously, so we need to add a prototype method Chain.prototype.next to the Chain class, indicating that the request is manually passed to the next node in the chain of responsibility:

        Chain.prototype.next= function(){
    
    
            return this.successor && this.successor.passRequest.apply( this.successor, arguments );
        };

Let's look at an example of an asynchronous chain of responsibility:

        var fn1 = new Chain(function(){
    
    
            console.log( 1 );
            return 'nextSuccessor';
        });

        var fn2 = new Chain(function(){
    
    
            console.log( 2 );
            var self = this;
            setTimeout(function(){
    
    
              self.next();
            }, 1000 );
        });

        var fn3 = new Chain(function(){
    
    
            console.log( 3 );
        });

        fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
        fn1.passRequest();

Now we get a special chain, the request is passed among the nodes in the chain, but the node has the right to decide when to pass the request to the next node. It is conceivable that with the asynchronous chain of responsibility plus the command mode (encapsulating the ajax request into a command object, please refer to Chapter 9 for details), we can easily create an asynchronous ajax queue library.

Pros and cons of the Chain of Responsibility pattern

As mentioned earlier, the biggest advantage of the chain of responsibility model is that it decouples the complex relationship between the request sender and the N receivers. Since you don’t know which node in the chain can handle your request, you only need to send the request Just pass it to the first node,
insert image description here
insert image description here

advantage

After improvement with the chain of responsibility model: In the example of the mobile phone store, we were forced to maintain a huge function full of conditional branch statements. In the example, only one log statement was printed during the purchase process. In fact, in actual development, there are more things to do here, such as popping up different floating layer prompts according to the type of order, rendering different UI nodes, combining different parameters and sending them to different cgis, etc. After using the responsibility chain mode, each order has its own processing function without affecting each other.

shortcoming

The Chain of Responsibility mode adds some node objects in the program. Perhaps in a certain request delivery process, most of the nodes do not play a substantive role. Their role is only to let the request pass on. From the perspective of performance, we To avoid performance loss caused by too long chain of responsibility.

Realize Chain of Responsibility with AOP

unction.prototype.after function, so that when the first function returns 'nextSuccessor', the request will be passed to the next function. Whether it returns the string 'nextSuccessor' or false is just a contract. Of course, we can also let the function Return false to pass the request on, the 'nextSuccessor' string was chosen because it seemed more expressive for our purposes.

        Function.prototype.after = function( fn ){
    
    
            var self = this;
            return function(){
    
    
              var ret = self.apply( this, arguments );
              if ( ret === 'nextSuccessor' ){
    
    
                  return fn.apply( this, arguments );
              }

              return ret;
            }
        };

        var order = order500yuan.after( order200yuan ).after( orderNormal );

        order( 1, true, 500 );    // 输出:500元定金预购,得到100优惠券
        order( 2, true, 500 );    // 输出:200元定金预购,得到50优惠券
        order( 1, false, 500 );   // 输出:普通购买,无优惠券

Using AOP to implement the chain of responsibility is simple and ingenious, but this method of stacking functions also superimposes the scope of the function. If the chain is too long, it will also have a greater impact on performance.

Guess you like

Origin blog.csdn.net/weixin_45172119/article/details/128724169