WEB前端的設(shè)計(jì)模式、責(zé)任鏈模式

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

責(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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容