App下載

javascript設(shè)計(jì)模式:職責(zé)鏈模式

猿友 2021-02-26 15:14:49 瀏覽數(shù) (8089)
反饋

職責(zé)鏈的定義:使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象能處理它為止,傳遞鏈中的這些對(duì)象就叫節(jié)點(diǎn)。

需求背景: 一個(gè)電商網(wǎng)站,用戶交500定金且定金已付時(shí),可享受500優(yōu)惠券且不受貨物數(shù)量限制;用戶交200定金且定金已付時(shí),可享受500優(yōu)惠券且不受貨物數(shù)量限制;用戶不交定金時(shí)受貨物數(shù)量限制,有貨時(shí)原價(jià)買,無貨時(shí)則無法買。

原始版本, ?if else?一路判斷

var buyOrder = function(orederType, pay, stock){
    if(orederType == 1){
        if(pay){
            console.log('500優(yōu)惠券');
        }else {
            if(stock > 0){
                console.log('普通購(gòu)物頁面');
            }else {
                console.log('已無貨');
            }
        }
    }else if(orederType == 2){
        if(pay){
            console.log('200優(yōu)惠券');
        }else {
            if(stock > 0){
                console.log('普通購(gòu)物頁面');
            }else {
                console.log('已無貨');
            }
        }
    }else if(orederType == 3){
        if(stock > 0){
            console.log('普通購(gòu)物頁面');
        }else {
            console.log('已無貨');
        }
    }
}

buyOrder(1, true, 600)

改進(jìn)版本

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

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

var orderNormal = function(orderType, pay , stock){
    if(stock > 0){
        console.log('普通購(gòu)物頁面');
    }else {
        console.log('已無貨');
    }
}

order500(3, true, 0)

優(yōu)化版本1:
同步的職責(zé)鏈

//3個(gè)訂單函數(shù) ,它們都是節(jié)點(diǎn)函數(shù)
var order500 = function(orderType, pay , stock){
    if(orderType == '1' && pay == true){
        console.log('500優(yōu)惠券');
    }else {
        return 'nextSuccessor';     //我不知道下個(gè)節(jié)點(diǎn)是誰,反正把請(qǐng)求往后傳遞
    }
}

var order200 = function(orderType, pay , stock){
    if(orderType == '2' && pay == true){
        console.log('200優(yōu)惠券');
    }else {
        return 'nextSuccessor';     //我不知道下個(gè)節(jié)點(diǎn)是誰,反正把請(qǐng)求往后傳遞
    }
}

var orderNormal = function(orderType, pay , stock){
    if(stock > 0){
        console.log('普通購(gòu)物頁面');
    }else {
        console.log('已無貨');
    }
}

//職責(zé)構(gòu)造函數(shù)
var Chain = function(fn){
    this.fn = fn;
    this.successor = null;
}

Chain.prototype.setNextSuccessor = function(successor){     //設(shè)置職責(zé)順序方法
    this.successor = successor
}

Chain.prototype.passRequest = function(){       //請(qǐng)求傳遞
    var ret = this.fn.apply(this, arguments)

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

    return ret;
}

//把3個(gè)訂單函數(shù)分別包裝成職責(zé)鏈的節(jié)點(diǎn)
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)

//然后指定節(jié)點(diǎn)在職責(zé)鏈中的順序
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)

//最后把請(qǐng)求傳遞給第一個(gè)節(jié)點(diǎn),開啟職責(zé)鏈模式傳遞
chainOrder500.passRequest(1, true, 500)     //500優(yōu)惠券
chainOrder500.passRequest(3, true, 20)      //普通購(gòu)物頁面
chainOrder500.passRequest(3, true, 0)       //已無貨

//此時(shí)如果中間有需求改動(dòng),只需如此做: 
var order300 = function(){
    if(orderType == '3' && pay == true){
        console.log('300優(yōu)惠券');
    }else {
        return 'nextSuccessor';     //我不知道下個(gè)節(jié)點(diǎn)是誰,反正把請(qǐng)求往后傳遞
    }
}
var chainOrder300 = new Chain(order300)     //添加新職責(zé)節(jié)點(diǎn)
chainOrder500.setNextSuccessor(chainOrder300)
chainOrder300.setNextSuccessor(chainOrder300)   //修改職責(zé)鏈順序
chainOrder200.setNextSuccessor(chainOrderNormal)

//這樣,就可以完全不必去理會(huì)原來的訂單函數(shù)代碼,只需增加一個(gè)節(jié)點(diǎn),然后重新設(shè)置職責(zé)鏈中的相關(guān)節(jié)點(diǎn)的順序就行。

優(yōu)化版本2:異步的職責(zé)鏈

在實(shí)際開發(fā)中,經(jīng)常會(huì)遇到 一些異步的問題,比如要在節(jié)點(diǎn)函數(shù)中發(fā)起一個(gè)ajax請(qǐng)求,異步請(qǐng)求返回的結(jié)果才能決定是否繼續(xù)在職責(zé)鏈中passRequest

可以給Chain類再增加一個(gè)原型方法:

//職責(zé)構(gòu)造函數(shù)
var Chain = function(fn){
    this.fn = fn;
    this.successor = null;
}

Chain.prototype.setNextSuccessor = function(successor){     //設(shè)置職責(zé)順序方法
    this.successor = successor
}

Chain.prototype.passRequest = function(){       //請(qǐng)求傳遞
    var ret = this.fn.apply(this, arguments)

    if(ret === 'nextSuccessor'){    //傳遞給職責(zé)鏈中的下一個(gè)節(jié)點(diǎn)
        return this.successor && this.successor.passRequest.apply(this.successor, arguments)
    }

    return ret;
}

//新增,表示手動(dòng)傳遞請(qǐng)求給職責(zé)鏈中的下一個(gè)節(jié)點(diǎn)
Chain.prototype.next = function(){
    return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}


//異步職責(zé)鏈例子
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);
})


//指定節(jié)點(diǎn)在職責(zé)鏈中的順序
fn1.setNextSuccessor(fn2)
fn2.setNextSuccessor(fn3)

//把請(qǐng)求傳遞給第一個(gè)節(jié)點(diǎn),開始節(jié)點(diǎn)傳遞
fn1.passRequest()

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

//這是一個(gè)異步職責(zé)鏈,請(qǐng)求在職責(zé)鏈節(jié)點(diǎn)中傳遞,但節(jié)點(diǎn)有權(quán)利決定什么時(shí)候 把請(qǐng)求交給下一個(gè)節(jié)點(diǎn)。這樣可以創(chuàng)建一個(gè)異步ajax隊(duì)列庫。 

tips:

這里補(bǔ)充個(gè)知識(shí)點(diǎn):“短路求值” ?&&? 會(huì)返回第一個(gè)假值?(0, null, "", undefined, NaN)?,而? || ?則會(huì)返回第一個(gè)真值。

?var x = a || b || c? 等價(jià)于:

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

var x = a && b && c 等價(jià)于:

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

所以 ?&&? 有時(shí)候會(huì)用來代替 ?if (expression) doSomething()?,轉(zhuǎn)成 ?&&?方式就是 ?expression && doSomething()?。

而 ?||? 比較用來在函數(shù)中設(shè)置默認(rèn)值,比如:

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

不過還需要看具體的使用場(chǎng)景,就比如如果要求 ?doSomething()? 傳入的 arg1 為一個(gè)數(shù)值,則上面的寫法就會(huì)出現(xiàn)問題(在傳入 0 的時(shí)候被認(rèn)為是一個(gè)假值而使用默認(rèn)值)。

現(xiàn)在個(gè)人比較常用的方法只判斷是否與 ?undefined? 相等,比如

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

職責(zé)鏈模式的優(yōu)勢(shì):解耦請(qǐng)求發(fā)送者和 N 個(gè)接收者之間的復(fù)雜關(guān)系,由于不知道鏈條中的哪個(gè)節(jié)點(diǎn)可以處理你發(fā)出的請(qǐng)求,所以只需把請(qǐng)求傳遞給第一個(gè)節(jié)點(diǎn)就行。

如果在實(shí)際開發(fā)中,當(dāng)維護(hù)一個(gè)含有多個(gè)條件分支語句的巨大函數(shù)時(shí)時(shí),可以使用職責(zé)鏈模式。鏈中的節(jié)點(diǎn)對(duì)象可以靈活拆分重組,增加刪除節(jié)點(diǎn),且無需改動(dòng)其他節(jié)點(diǎn)函數(shù)內(nèi)的代碼。


0 人點(diǎn)贊