js-Flexible and Splittable Responsibility Chain Model

Refer to JavaScript Design Patterns and Development Practices, which has been explored and strongly recommended.

Definition

Avoid coupling requests between sender and receiver so that multiple objects may receive requests, join them into a chain, and pass requests along that chain until an object processes it.

The above figure is the image representation of the responsibility chain pattern.

scene

Scenarios in real work:

Example 1:

Suppose a team hasProduct + Design + Front End + Back End + Test, and stipulates that every task should start from the product, and can not find people privately. Now you need to make a poster effect map temporarily. No doubt, this needs to be done by the designer, but the person who publishes the task just needs to drop the task to the first node (product), and does not need to know who is dealing with it.

  • Task 1: Make a poster image

Task Release - Product (Not Me) - Design (Me), so the task is handled at the Designer node.

  • Task 2: Database Data Processing

Task Release - Product (Not Me) - Design (Not Me) - Front End (Not Me) - Back End (Unfortunately, Me!)

Example 2:

During the rush hour in the morning and evening, there were too many people to find a ticket salesman, so I asked others to help me pass the money forward. Finally, the money was passed to the salesman.

That's the responsibility chain pattern in reality, where tasks are delegated to the first node and passed along the chain until someone can handle it.

How do you write it?

Reference Article

For example: for an online shop, members have three grades A, B and C, A-level members get 8% discount on shopping, B-level members get 9% discount on shopping, and C-level members get 95% discount on shopping.

According to the principle of responsibility chain mode, when the first recipient object cannot process the request, the request is handed over to the next recipient object.

// 1. Define each node
const shoppingA = (grade ) => {
    if(grade === 'A') {
        console.log( `Your rating is${grade},You get a 20% discount` )
    } else {
        return 'nextSuccessor'
    }
}

const shoppingB = (grade) => {
    if(grade === 'B') {
        console.log( `Your rating is${grade},You get a 10% discount` )
    } else {
        return 'nextSuccessor'
    }
}

const shoppingC = (grade) => {
    if(grade === 'C') {
        console.log( `Your rating is${grade},You get a 95% discount` )
    } else {
        return 'nextSuccessor'
    }
}

// 2. Create a class of chains to connect nodes
class Chain {
    constructor(fn){
        this.fn = fn
        this.successor = null
    }

    // Set Next Node
    setNextSuccessor(successor) {
        return this.successor = successor
    }

    // How to handle tasks when they reach this node
    passRequest(grade) {
        var ret = this.fn(grade) 
        if(ret === 'nextSuccessor') {
            return this.successor && this.successor.passRequest.apply(this.successor, arguments)
        }
        return ret
    }
}

// 3. Call
const chainA = new Chain(shoppingA) // Instantiate A Node
const chainB = new Chain(shoppingB) // Instantiate B Node
const chainC = new Chain(shoppingC) // Instantiate C Node


chainA.setNextSuccessor(chainB) // The next section of node A is set to B
chainB.setNextSuccessor(chainC) // The next section of node B is set to C
// Now this chain is chainA-chainB-chainC


// hold 'A', 'B', 'C' Equal parameters go directly to the first node chainA Incoming is sufficient

chainA.passRequest('A') -> Your rating is A,You get a 20% discount
chainA.passRequest('B') -> Your rating is B,You get a 10% discount
chainA.passRequest('C') -> Your rating is C,You get a 95% discount

// 4. Suppose one day you need to add 'S' Members of the level are offered a 50% discount, just add one node

// Add a specific discount method first
const shoppingS = (grade) => {
    if(grade === 'S') {
        console.log( `Your rating is${grade},You get a 50% discount` )
    } else {
        return 'nextSuccessor'
    }
}

// Then add an S node after the original node
const chainS = new Chain(shoppingS)
chainC.setNextSuccessor(chainS)

Copy Code

The responsibility chain model is like a chain, which can flexibly add, delete and modify nodes.

Why?

It may be said that writing so much on it is not as convenient as using a shuttle directly if-else.

So write the following code

function shopping(grade){
    if(grade === 'A'){
        console.log('Your rating is ${grade},You get a 20% discount')
    } else if(grade === 'B'){
        console.log('Your rating is ${grade},You get a 10% discount')
    } else if(grade === 'C'){
        console.log('Your rating is ${grade},You get a 95% discount')
    }
}
Copy Code

Is this not simple and clear?

Assuming that you now need to add members at the'S'level, with a 50% discount, you need to go inside the shopping function to make changes.This modification violates the open-close principle and will make the shopping function very bloated if you need to add other levels of membership in the future.

The above example has only one variable influencing factor and does not show the advantage of the responsibility chain model very strongly. It is intended to let us know the responsibility chain model first. Please see the classic example below.

Classic examples

Suppose we are responsible for an electronics website that sells mobile phones. After two rounds of reservations with a deposit of 500 yuan and a deposit of 200 yuan, respectively (orders have been generated at this time), we are now in the stage of formal purchase.The company has certain preferential policies for users who have paid down deposits.After the formal purchase, users who have already paid 500 yuan deposit will receive a 100 yuan store coupon. Users who have 200 yuan deposit can receive 50 yuan coupon. Users who have not paid the deposit before can only enter the normal purchase mode, that is, there is no coupon, and they are not guaranteed to buy it in the case of Limited inventory.

// Not using responsibility chain model
var order = function( orderType, pay, stock ){
    if ( orderType === 1 ){ // 500 yuan deposit purchase mode
        if ( pay === true ){ // Deposit paid
            console.log( '500 Yuan deposit advance purchase, Get 100 coupons' );
        } else{ // No deposit paid, downgraded to normal purchase mode
            if ( stock > 0 ){ // Mobile phones for regular purchases are still in stock
                console.log( 'Ordinary Purchase, No coupons' );
            }else{
                console.log( 'Mobile phone is out of stock' );
            } 
        }
    } else if ( orderType === 2 ){ 
        if ( pay === true ){ // 200 yuan deposit purchase mode
            console.log( '200 Yuan deposit advance purchase, Get 50 coupons' ); 
        }else{
            if ( stock > 0 ){
                console.log( 'Ordinary Purchase, No coupons' );
            }else{
                console.log( 'Mobile phone is out of stock' );
            } 
        }
    } else if (orderType === 3) {
        if ( stock > 0 ){
            console.log( 'Ordinary Purchase, No coupons' ); 
        } else{
            console.log( 'Mobile phone is out of stock' ); 
        }
    } 
};
order( 1 , true, 500); // Output: 500 yuan deposit preorder, get 100 coupons

Copy Code

Despite the expected results, the order function is not only too large to read, but it also needs to be modified frequently.Now it works, but the next maintenance is a nightmare.

Now, we're refactoring the code with the responsibility chain pattern

Ideas: First, divide 500 yuan order, 200 yuan order and ordinary purchase into three functions.Next, the three fields orderType, pay, stock are passed as parameters to the 500-yuan order function. If the function does not meet the processing conditions, the request is passed to the following 200-yuan order function. If the 200-yuan order function still cannot process the request, the request is continued to be passed to the ordinary purchase function.

var order500 = function( orderType, pay, stock ){ 
    if ( orderType === 1 && pay === true ){
        console.log( '500 Pre-order with RMB deposit and get 100 coupons' ); 
    } else{
        return 'nextSuccessor'; // I don't know who the next node is, but instead pass the request back 
    }
};
var order200 = function( orderType, pay, stock ){ 
    if ( orderType === 2 && pay === true ){
        console.log( '200 Pre-order with a deposit of RMB 50 coupons' ); 
    } else{
        return 'nextSuccessor'; // I don't know who the next node is, but instead pass the request back 
    }
};
var orderNormal = function( orderType, pay, stock ){
    if ( stock > 0 ){ 
        console.log( 'Ordinary purchase, no coupons' ); 
    } else{
        console.log( 'Mobile phone is out of stock' ); 
    }
};

// Chain.prototype.setNextSuccessor specifies the next node in the chain
// Chain.prototype.passRequest passes a request to a node
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; 
};
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

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

chainOrder500.passRequest( 1, true, 500 );   // Output: 500 yuan deposit for pre-purchase, get 100 coupons
chainOrder500.passRequest( 2, true, 500 );   // Output: 200 yuan deposit, 50 coupons
chainOrder500.passRequest( 3, true, 500 );   // Output: Ordinary purchase, no coupons
chainOrder500.passRequest( 1, false, 0 );    // Output: Mobile phone is out of stock

Copy Code

With improvements, we have the freedom and flexibility to increase, remove and modify the order of nodes in the chain. If a website operator comes up with another 300 yuan deposit purchase one day, we can add a node in the chain.

var order300 = function(){
 // Slight implementation
};
chainOrder300= new Chain( order300 ); 
chainOrder500.setNextSuccessor( chainOrder300); 
chainOrder300.setNextSuccessor( chainOrder200);

Copy Code

Asynchronous Responsibility Chain

In real-world development, we often encounter asynchronous problems, such as an ajax asynchronous request in a node function that returns a result that determines whether to continue passRequest in the responsibility chain.It is no longer meaningful to have node functions return "nextSuccessor" synchronously at this point, so add another prototype method to the Chain class, Chain.prototype.next, to indicate that the request is passed manually to the next node in the responsibility chain.

Chain.prototype.next= function(){
    return this.successor && this.successor.passRequest.apply( this.successor, arguments );
};
/* Asynchronous Responsibility Chain */
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(); //Asynchronously return the result and pass it to the next node!! 
    }, 1000 );
});
var fn3 = new Chain(function(){
    console.log( 3 );
});
fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 ); 
fn1.passRequest();
Copy Code

Now we have a special chain where requests are passed through the nodes in the chain, but the nodes have the right to decide when to hand over requests to the next node.As you can imagine, an asynchronous responsibility chain plus command mode (encapsulating an ajax request as a command object) makes it easy to create an asynchronous ajax queue library.

Implement Responsibility Chain with AOP

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 = order500.after( order200 ).after( orderNormal );
order( 1, true, 500 );    // Output: 500 yuan deposit for pre-purchase, get 100 coupons 
order( 2, true, 500 );    // Output: 200 yuan deposit, 50 coupons 
order( 1, false, 500 );   // Output: Ordinary purchase, no coupons

Copy Code

Using AOP to implement responsibility chains is simple and clever, but this way of overlaying functions together also overlays the scope of functions, which can have a significant impact on performance if the chain is too long.

Advantages and disadvantages

Advantages: The greatest advantage of the responsibility chain is that it decouples the complex relationship between the sender of the request and the N recipients, simply passing the request to the first node.

Disadvantage: There is no guarantee that a request will be processed by the nodes in the chain.If none of the nodes can process, the request cannot be answered, and will either leave the chain directly or throw an exception. We can add a bottom-secured receiver node at the end of the chain to process requests that the node cannot process before.In addition, the responsibility chain mode makes some node objects in the program. Maybe most of the nodes do not play a substantial role in a request transfer. Their role is only to pass the request on. In terms of performance, we should avoid the performance loss caused by too long responsibility chain.

Summary

In JavaScript development, the responsibility chain pattern is one of the most easily overlooked patterns.In fact, as long as it is used properly, the responsibility chain model can help us manage our code and reduce the coupling between the originating and processing objects.The number and order of nodes in the responsibility chain can change freely, and we can decide which nodes are included in the chain at run time.

Whether it is the scope chain, the prototype chain, or the event bubbles in the DOM nodes, we can find the shadow of the responsibility chain pattern.Responsibility chain patterns can also be combined with composition patterns to connect components to parent components or to improve the efficiency of combining objects.

Tags: Mobile Javascript Database

Posted on Sun, 17 May 2020 20:22:04 -0400 by WeAnswer.IT