The chain of responsibility pattern is defined as avoiding coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request, connecting the objects into a chain, and passing the request along the chain until one object processes it. The name of the chain of responsibility pattern is very descriptive. A series of objects that might handle a request are connected in a chain, and the request is passed between these objects until an object that can handle it is encountered, which we call nodes in the chain
The story background
Let’s say we are in charge of an e-commerce website selling mobile phones. After two rounds of bookings of 500 yuan and 200 yuan respectively (the order has been generated at this point), we are now in the stage of formal purchase. The company has a certain preferential policy for users who have paid a deposit. After the official purchase, users who have paid 500 yuan deposit will receive 100 yuan mall coupons, users who have paid 200 yuan deposit can receive 50 yuan coupons, and users who did not pay the deposit before can only enter the ordinary purchase mode, that is, there is no coupon, and in the case of limited inventory may not be guaranteed to buy.
Code implementation (without chain of Responsibility pattern)
var order = function( orderType, pay, stock ){
if(orderType === 1){// 500 yuan deposit purchase modeif ( pay === true){// Deposit paid console.log('500 yuan deposit, get 100 coupons' );
} else{// No deposit paid, demoted to normal purchase modeif(stock > 0){// For general purchases there is also inventory console.log('Regular purchase, no coupons' );
}else{
console.log( 'Lack of mobile phone stock'); }}}else if ( orderType === 2 ){
if ( pay === true){// 200 yuan deposit purchase mode console.log('200 yuan deposit, get 50 coupons' );
}else{
if ( stock > 0 ){
console.log( 'Regular purchase, no coupons' );
}else{
console.log( 'Lack of mobile phone stock'); }}}else if (orderType === 3) {
if ( stock > 0 ){
console.log( 'Regular purchase, no coupons' );
} else{
console.log( 'Lack of mobile phone stock'); }}}; order( 1 ,true, 500); // Output: 500 yuan deposit pre-order, get 100 couponsCopy the code
Refactoring approach
Now let’s refactor this code using the chain of responsibility pattern, first dividing the $500 order, $200 order, and ordinary purchase into three functions. Next, we pass orderType, pay, and stock as parameters to the 500 yuan order function. If the function does not meet the processing conditions, we pass the request to the 200 yuan order function. If the 200 yuan order function still cannot process the request, Passes the request to the normal purchase function.
Code refactoring (using the chain of Responsibility pattern)
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 yuan deposit, get 100 coupons' );
} else{
return 'nextSuccessor'; // I don't know who the next node is. var order200 =function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 yuan deposit, get 50 coupons' );
} else{
return 'nextSuccessor'; // I don't know who the next node is. var orderNormal =function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( 'Regular purchase, no coupons' );
} else{
console.log( 'Lack of mobile phone stock'); }}; / / Chain. Prototype. SetNextSuccessor specify the next node in the Chain / / Chain. The prototype. The var Chain = passRequest transfer request to a nodefunction( 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 and advance, get 100 coupons chainOrder500. PassRequest (2,true, 500); / / output: 200 yuan deposit and advance, get 50 coupons chainOrder500. PassRequest (3,true, 500); / / output: ordinary purchase, no coupons chainOrder500. PassRequest (1,false, 0); // Output: Not enough phones in stockCopy the code
Through improvement, we can freely and flexibly increase, remove and modify the sequence of nodes in the chain, if one day the website operation staff came up with support for 300 yuan deposit to buy, then we can increase a node in the chain
var order300 = function(){// implement a little}; chainOrder300= new Chain( order300 ); chainOrder500.setNextSuccessor( chainOrder300); chainOrder300.setNextSuccessor( chainOrder200);Copy the code
Asynchronous chains of responsibility
In real development, we often encounter some asynchronous problems. For example, we need to make an Ajax asynchronous request in the node function, and the result of the asynchronous request can decide whether to continue the passRequest in the responsibility chain. It would not make sense to let node functions return the “nextsucceeded” synchronously at this point, so add another prototype method to the Chain class, chain.prototype. next, to manually pass requests to the next node in the Chain of responsibilities
Chain.prototype.next= function() {returnthis.successor && this.successor.passRequest.apply( this.successor, arguments ); }; 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();
Copy the code
Now we have a special chain where the request is passed through the nodes in the chain, but the nodes have the power to decide when to pass the request to the next node. As you can imagine, with an asynchronous responsibility chain and a command pattern that encapsulates Ajax requests as command objects, you can easily create an asynchronous Ajax queue library.
Implement the chain of responsibility 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 );
}
returnret; }}; var order = order500yuan.after( order200yuan ).after( orderNormal ); order( 1,true, 500); // Output :500 yuan deposit pre-order, get 100 coupons order(2,true, 500); // Output :200 yuan deposit pre-order, get 50 coupons order(1,false, 500); // Output: normal purchase, no couponCopy the code
Implementing a chain of responsibilities with AOP is simple and clever, but this way of stacking functions together also adds the scope of functions, which can also have a significant impact on performance if the chain is too long
summary
The chain of responsibility pattern is one of the most overlooked patterns in JavaScript development. In fact, when used properly, the chain of responsibility pattern can help us manage our code very well, reducing the coupling between the object that initiates the request and the object that processes it. The number and order of nodes in the responsibility chain can vary freely, and we can decide at run time which nodes to include in the chain.
Series of articles:
JavaScript Design Patterns and Development Practices