前話
文章末端有漂亮妹妹的圖片哦

責(zé)任鏈模式
什么是責(zé)任鏈模式
責(zé)任鏈(Chain of Responsibility)模式的定義:為了避免請(qǐng)求發(fā)送者與多個(gè)請(qǐng)求處理者耦合在一起,將所有請(qǐng)求的處理者通過前一對(duì)象記住其下一個(gè)對(duì)象的引用而連成一條鏈;當(dāng)有請(qǐng)求發(fā)生時(shí),可將請(qǐng)求沿著這條鏈傳遞,直到有對(duì)象處理它為止。(此處引自 gof 設(shè)計(jì)模式)
在責(zé)任鏈模式中,客戶只需要將請(qǐng)求發(fā)送到責(zé)任鏈上即可,無須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞過程,所以責(zé)任鏈將請(qǐng)求的發(fā)送者和請(qǐng)求的處理者解耦了。
責(zé)任鏈模式是一種對(duì)象行為型模式,其主要優(yōu)點(diǎn)如下:
1.降低了對(duì)象之間的耦合度。該模式使得一個(gè)對(duì)象無須知道到底是哪一個(gè)對(duì)象處理其請(qǐng)求以及鏈的結(jié)構(gòu),發(fā)送者和接收者也無須擁有對(duì)方的明確信息。
2.增強(qiáng)了系統(tǒng)的可擴(kuò)展性??梢愿鶕?jù)需要增加新的請(qǐng)求處理類,滿足開閉原則。
3.增強(qiáng)了給對(duì)象指派職責(zé)的靈活性。當(dāng)工作流程發(fā)生變化,可以動(dòng)態(tài)地改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序,也可動(dòng)態(tài)地新增或者刪除責(zé)任。
4.責(zé)任鏈簡(jiǎn)化了對(duì)象之間的連接。每個(gè)對(duì)象只需保持一個(gè)指向其后繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。
5.責(zé)任分擔(dān)。每個(gè)類只需要處理自己該處理的工作,不該處理的傳遞給下一個(gè)對(duì)象完成,明確各類的責(zé)任范圍,符合類的單一職責(zé)原則。
其主要缺點(diǎn)如下。
1.不能保證每個(gè)請(qǐng)求一定被處理。由于一個(gè)請(qǐng)求沒有明確的接收者,所以不能保證它一定會(huì)被處理,該請(qǐng)求可能一直傳到鏈的末端都得不到處理。
2.對(duì)比較長的職責(zé)鏈,請(qǐng)求的處理可能涉及多個(gè)處理對(duì)象,系統(tǒng)性能將受到一定影響。
3.職責(zé)鏈建立的合理性要靠客戶端來保證,增加了客戶端的復(fù)雜性,可能會(huì)由于職責(zé)鏈的錯(cuò)誤設(shè)置而導(dǎo)致系統(tǒng)出錯(cuò),如可能會(huì)造成循環(huán)調(diào)用。
其他說明
責(zé)任鏈模式,總的一個(gè)核心就是請(qǐng)求者不必知道是誰哪個(gè)節(jié)點(diǎn)對(duì)象處理的請(qǐng)求,由于處理請(qǐng)求的可以在不同對(duì)象下處理,所以請(qǐng)求者跟接受者是解耦的。
純的責(zé)任鏈:要求請(qǐng)求在這些對(duì)象鏈中必須被處理 ,而且一個(gè)節(jié)點(diǎn)處理對(duì)象,要么只處理請(qǐng)求,要么把請(qǐng)求轉(zhuǎn)發(fā)給下個(gè)節(jié)點(diǎn)對(duì)象處理;
不純的責(zé)任鏈:要求在責(zé)任鏈里 不一定會(huì)有處理結(jié)構(gòu) ,而且一個(gè)節(jié)點(diǎn)對(duì)象,即可以處理部分請(qǐng)求,并把請(qǐng)求再轉(zhuǎn)發(fā)下個(gè)節(jié)點(diǎn)處理;
javascript 中介者模式
責(zé)任鏈模式對(duì)前端開發(fā)來說可能有點(diǎn)陌生,但是看了前面的描述又感覺似曾相識(shí)
實(shí)際上 express、redux 里的 middleware 都可以簡(jiǎn)單理解為責(zé)任鏈模式的運(yùn)用
要實(shí)現(xiàn)中間件模式,最重要的實(shí)現(xiàn)細(xì)節(jié)是:
2.可以通過調(diào)用 use() 函數(shù)來注冊(cè)新的中間件
2.當(dāng)接收到需要處理的新數(shù)據(jù)時(shí),注冊(cè)的中間件在執(zhí)行流程中被依次調(diào)用。每個(gè)中間件都接受上一個(gè)中間件的執(zhí)行結(jié)果作為輸入值
3.每個(gè)中間件都可以停止數(shù)據(jù)的進(jìn)一步處理,只需要簡(jiǎn)單地不調(diào)用它的回調(diào)函數(shù)或者將錯(cuò)誤傳遞給回調(diào)函數(shù)。當(dāng)發(fā)生錯(cuò)誤時(shí),通常會(huì)觸發(fā)執(zhí)行另一個(gè)專門處理錯(cuò)誤的中間件
項(xiàng)目實(shí)戰(zhàn)
通用中間件開發(fā)
class Middleware {
constructor() {
this.$cache = []
this.$middlewares = []
}
// 注冊(cè)中間件
use() {
[...arguments].forEach(item => {
if (typeof item === 'function') {
this.$cache.push(item)
}
})
return this
}
/**
* 每個(gè)中間件只有兩個(gè)形參 第一是傳進(jìn)來的參數(shù) 第二個(gè)是調(diào)用下一個(gè)中間件的函數(shù)
* 中間件的執(zhí)行順序是根據(jù)你注冊(cè)中間件的順序來去調(diào)用的
*/
next(params) {
while (this.$middlewares.length) {
const ware = this.$middlewares.shift()
ware.call(this, params, this.next.bind(this))
}
}
execute(params) {
this.$middlewares = this.$cache.map(fn => { // 復(fù)制一份
return fn;
});
this.next(params)
}
}
export default Middleware
復(fù)制代碼
通用中間件使用 ajax
const middleware = new Middleware()
function transform(options, next) {
console.log('before', options.data);
options.data.age = Number(options.data.age)
next(options); // 通過驗(yàn)證
}
function validate(options, next) {
console.log('validate', options.data);
next(options); // 通過驗(yàn)證
}
function send(options, next) {
setTimeout(function () { // 模擬異步
console.log('send', options.data);
next();
}, 100);
}
middleware.use(transform).use(validate).use(send)
middleware.execute({ data: { name: 'cookie', age: '20' } });
復(fù)制代碼
如上我們?cè)诎l(fā)送請(qǐng)求之前加入了類型轉(zhuǎn)換、數(shù)據(jù)校驗(yàn),將數(shù)據(jù)的業(yè)務(wù)處理使用中間件模式剝離,使得處理過程模塊化,維護(hù)性提升。
中間件升級(jí)-事件回調(diào)
/**
* 注冊(cè)事件
* @param {String} name 事件名稱
* @param {Function (params)} callback 回調(diào)函數(shù)
*/
on(name, callback) {
if (typeof callback === 'function') {
this.$events[name] = callback
} else {
throw '事件回調(diào)必須為函數(shù)'
}
}
/**
* 發(fā)射(觸發(fā))事件
* @param {String} name 事件名稱
* @param {Any} params 回調(diào)參數(shù)
*/
emit(name, params) {
if (this.$events[name]) {
let callback = this.$events[name]
callback.call(this, params)
} else {
throw '沒有注冊(cè)這個(gè)事件'
}
}
復(fù)制代碼
每個(gè)中間件的過程都是不可控制的,全部都交由中間類去統(tǒng)一調(diào)用,我們可以加入事件回調(diào),方便我們?cè)谥虚g件處理過程中擁有額外的邏輯能力
將上述的使用方法再改造一下,方便實(shí)際業(yè)務(wù)中使用
function send(options, next) {
this.emit('request', options)
setTimeout(() => { // 模擬異步
console.log('send', options.data);
this.emit('response', options)
options.promise.resolve({ data: options.data })
}, 100);
}
// 請(qǐng)求之前的回調(diào)函數(shù)
middleware.on('request', params => {
// 在這里可以做請(qǐng)求之前的一些處理,比如添加全局參數(shù)等
console.log(params, '再多做一些處理')
})
// 請(qǐng)求成功的回調(diào)函數(shù)
middleware.on('response', params => {
// 在這里可以做下請(qǐng)求成功的一些處理,比如全局loading什么的
console.log(params, '請(qǐng)求成功')
})
middleware.use(transform).use(validate).use(send)
middleware.executeFc({ data: { name: 'cookie', age: '20' } }).then(({ data }) => {
console.log('finally', data)
});
復(fù)制代碼
上述的項(xiàng)目實(shí)例是采用 ajax 來演示,實(shí)際通用的中間件類,可以在業(yè)務(wù)中將一些 流程化執(zhí)行的任務(wù) 拆分出來,例如表單驗(yàn)證、多重條件判斷等等
多種條件判斷
const middleware = new Middleware()
function judge1(options, next) { // 空數(shù)校驗(yàn)
if (!options.data) {
options.promise.reject({ data: false, msg: '數(shù)據(jù)為空' })
return
}
next(options); // 通過驗(yàn)證
}
function judge2(options, next) { // 判斷小于10
if (options.data < 10) {
options.promise.reject({ data: false, msg: '數(shù)據(jù)小于10' })
return
}
next(options); // 通過驗(yàn)證
}
function judge3(options, next) { // 判斷大于30
if (options.data < 30) {
options.promise.reject({ data: false, msg: '數(shù)據(jù)小于30' })
return
}
options.promise.resolve({ data: true, msg: '數(shù)據(jù)小于30' })
}
middleware.use(judge1).use(judge2).use(judge3)
middleware.executeFc({ data: 40 }).then(({ data }) => {
console.log('finally', data)
}).catch(({ msg }) => {
console.log(msg)
})
復(fù)制代碼
將流程化執(zhí)行的多種條件判斷通過中間件解耦,可以使得條件判斷方法更加清晰。一般當(dāng)你需要使用中介者來改造業(yè)務(wù)邏輯的時(shí)候,前端的項(xiàng)目確實(shí)有點(diǎn)復(fù)雜了。
結(jié)尾
如果你現(xiàn)在也想學(xué)習(xí)前端開發(fā)技術(shù),在學(xué)習(xí)前端的過程當(dāng)中有遇見任何關(guān)于學(xué)習(xí)方法,學(xué)習(xí)路線,學(xué)習(xí)效率等方面的問題,你都可以加入到我的Q群中:前114中6649后671,里面有許多前端學(xué)習(xí)資料以及2020大廠面試真題 點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā) 即可免費(fèi)獲取,希望能夠?qū)δ銈冇兴鶐椭?br>
