Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Javascript design pattern: Chain of duties mode


May 30, 2021 Article blog



Definition of a chain of duties: Gives multiple objects the opportunity to process requests, thus avoiding coupling between the sender and receiver of requests, connecting those objects into a chain, and passing the request along that chain until an object can handle it, and the objects in the delivery chain are called nodes.

Demand background: an e-commerce website, users pay 500 deposit and deposit has been paid, can enjoy 500 coupons and not limited by the number of goods;

Original version, if else all the way to judgment

var buyOrder = function(orederType, pay, stock){
    if(orederType == 1){
        if(pay){
            console.log('500优惠券');
        }else {
            if(stock > 0){
                console.log('普通购物页面');
            }else {
                console.log('已无货');
            }
        }
    }else if(orederType == 2){
        if(pay){
            console.log('200优惠券');
        }else {
            if(stock > 0){
                console.log('普通购物页面');
            }else {
                console.log('已无货');
            }
        }
    }else if(orederType == 3){
        if(stock > 0){
            console.log('普通购物页面');
        }else {
            console.log('已无货');
        }
    }
}

buyOrder(1, true, 600)

Improved version

var order500 = function(orderType, pay , stock){
    if(orderType == '1' && pay == true){
        console.log('500优惠券');
    }else {
        order200(orderType, pay , stock)
    }
}

var order200 = function(orderType, pay , stock){
    if(orderType == '2' && pay == true){
        console.log('200优惠券');
    }else {
        orderNormal(orderType, pay , stock)
    }
}

var orderNormal = function(orderType, pay , stock){
    if(stock > 0){
        console.log('普通购物页面');
    }else {
        console.log('已无货');
    }
}

order500(3, true, 0)

Optimised Version 1:
Synchronized chain of duties

//3个订单函数 ,它们都是节点函数
var order500 = function(orderType, pay , stock){
    if(orderType == '1' && pay == true){
        console.log('500优惠券');
    }else {
        return 'nextSuccessor';     //我不知道下个节点是谁,反正把请求往后传递
    }
}

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

var orderNormal = function(orderType, pay , stock){
    if(stock > 0){
        console.log('普通购物页面');
    }else {
        console.log('已无货');
    }
}

//职责构造函数
var Chain = function(fn){
    this.fn = fn;
    this.successor = null;
}

Chain.prototype.setNextSuccessor = function(successor){     //设置职责顺序方法
    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;
}

//把3个订单函数分别包装成职责链的节点
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)     //500优惠券
chainOrder500.passRequest(3, true, 20)      //普通购物页面
chainOrder500.passRequest(3, true, 0)       //已无货

//此时如果中间有需求改动,只需如此做: 
var order300 = function(){
    if(orderType == '3' && pay == true){
        console.log('300优惠券');
    }else {
        return 'nextSuccessor';     //我不知道下个节点是谁,反正把请求往后传递
    }
}
var chainOrder300 = new Chain(order300)     //添加新职责节点
chainOrder500.setNextSuccessor(chainOrder300)
chainOrder300.setNextSuccessor(chainOrder300)   //修改职责链顺序
chainOrder200.setNextSuccessor(chainOrderNormal)

//这样,就可以完全不必去理会原来的订单函数代码,只需增加一个节点,然后重新设置职责链中的相关节点的顺序就行。

Optimized version 2: Asynchronous chain of duty

In real-world development, there are often asynchronous problems, such as starting an ajax request in a node function, and the result of the asynchronous request returning before deciding whether to continue in the chain of duties

You can add another prototype method to the Chain class:

//职责构造函数
var Chain = function(fn){
    this.fn = fn;
    this.successor = null;
}

Chain.prototype.setNextSuccessor = function(successor){     //设置职责顺序方法
    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;
}

//新增,表示手动传递请求给职责链中的下一个节点
Chain.prototype.next = function(){
    return this.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)
fn2.setNextSuccessor(fn3)

//把请求传递给第一个节点,开始节点传递
fn1.passRequest()

//输出 1 2 ...(1秒后)... 3

//这是一个异步职责链,请求在职责链节点中传递,但节点有权利决定什么时候 把请求交给下一个节点。这样可以创建一个异步ajax队列库。 

tips:

Add a knowledge point here: "Short-circuit && will return the first false value (0, null, "", undefined, NaN) while || The first true value is returned.

var x = a || b || c equivalent to:

var x;
if(a){
    x = a;
} else if(b){
    x = b;
} else {
    x = c;
}

var x = a && b && c equivalent to:

var x = a;
if(a){
    x = b;
    if(b){
        x = c;
    }
}

So && used instead of if (expression) doSomething() and the && to expression && doSomething() is expression.

And || Comparisons are used to set default values in functions, such as:

function doSomething(arg1, arg2, arg3) {
    arg1 = arg1 || 'arg1Value';
    arg2 = arg2 || 'arg2Value';
}

However, you also need to look at specific usage scenarios, such as if doSomething() is required to pass in a value of arg1, then the above formulation will be problematic (when passed in 0 is considered a false value and the default value is used).

The more common way for individuals today is to determine whether they are equal to undefined for example

function doSomething(arg) {
    arg = arg !== void 0 ? arg : 0;
}

Advantages of the chain of responsibility model: The complex relationship between decoupling the request sender and the N recipients, knowing which node in the chain can handle your request, simply passing the request to the first node.

If in real-world development, you can use the chain of duties pattern when maintaining a large function that contains multiple conditional branch statements. The node objects in the chain have the flexibility to split and reorganize, adding deletion nodes without changing the code within other node functions.