iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Software Development

幫自己搞懂物件導向和設計模式系列 第 28

Chain of Responsibility 責任鏈模式

今天開始進入到 Behavioral design patterns,這一類的模式著重於物件之間的溝通與責任分配,就讓我們接下去一起看看吧

Chain of Responsibility 責任鏈模式

當有一個需要處理的需求(或請求)進入系統的時候,這個系統中負責的物件會串成一個鏈,然後讓這個需求依序被處理。在這個過程當中,每一個物件會

  • 決定如何處理這個需求
  • 決定是否將這個需求送往下一個物件

問題

假設有一間公司,準備發起一個新的 VIP 會員回饋方案,不過會員本身的點數和經驗需要到一個標準值才能參與。

如果我們的 Member 類別像是下面這樣

class Member {
  points: number;
  experience: number;
  constructor(points: number, experience: number){
    this.points = points
    this.experience = experience
  }
}

const memberA = new Member(1500, 2000)

接下來,我們就可以很簡單寫一個函式來進行判斷。譬如我們希望找到點數大於 1000、經驗值大於 1500 的會員來參與 VIP 會員回饋方案,實作如下:

const handler = (member: Member): boolean => {
  const { points, experience } = member
  return points > 1000 && experience > 1500
}

但如果未來我們有不同的回饋方案有不同的標準值、又或者說需要加入更多的判斷條件,那麼我們就需要建立更多的 handler 函式,或者是不斷的修改原有的 handler 函式。

如果我們可以用組合的方式,將不同的判斷函式組合成我們需要的函式,可以隨意調整,隨插隨用,是不是就會很方便呢?

接下來讓來看看責任鏈模式可以怎麼處理這個問題

實作

首先,我們建立 Handler 介面,和一個抽象類別。這裡我們定義 handler 當中需要有兩個方法,一個是處理需求的 handle 方法,另一個是記錄下一個 handler 的位置

interface Handler {
  handle(member: Member): boolean
  next(handler: Handler): Handler
}

abstract class AbstractHandler implements Handler {
  protected nextHandler: Handler

  next(handler: Handler): Handler {
    this.nextHandler = handler
    return handler
  }

  handle(member: Member): boolean {
    return false
  }
}

接著,我們就可以分別建立處理 points 和處理 experience 的 handler

class PointsHandler extends AbstractHandler {
  private limit: number;

  constructor(limit: number){
    super()
    this.limit = limit
  }

  handle(member: Member): boolean {
    if (member.points > this.limit) {
      return this.nextHandler
        ? this.nextHandler.handle(member) && true
        : true;
    }
    return false;
  }
}

class ExperienceHandler extends AbstractHandler {
  private limit: number;

  constructor(limit: number){
    super()
    this.limit = limit
  }

  handle(member: Member): boolean {
    if (member.experience > this.limit) {
      return this.nextHandler
        ? this.nextHandler.handle(member) && true
        : true;
    }
    return false;
  }
}

這裡我們做了幾件事情

  1. 在建立實例的時候,會設定判斷的條件 (limit)
  2. handle 方法當中,我們會進行條件判斷,如果不通過,就會直接回傳 false;如果通過,就會看之後的 handler 是否通過,來決定最後的回傳值

接下來,我們就可以分別建立 pointsHandler 和 experienceHandler

const pointsHandler = new PointsHandler(1000)
const experienceHandler = new ExperienceHandler(1500)

然後,記得把他們給串連一起

pointsHandler.next(experienceHandler)

最後,我們就可以用來判斷不同 member 的狀況

const memberA = new Member(1500, 2000)
const memberB = new Member(1250, 1250)
const memberC = new Member(750, 750)

pointsHandler.handle(memberA) // true
pointsHandler.handle(memberB) // false,因為 experience 不夠
pointsHandler.handle(memberC) // false,因為 points 和 experience 不夠

所以未來如果我們想要調整判斷的條件限制(譬如降低 points 條件至 500),那麼只要透過 PointsHandlerExperienceHandler 類別建立新的實例即可。

如果我們想要調整判斷的方式(譬如移除 points,新增 level),那麼我們就可以依賴 AbstractHandler 實作另外一個新的 handler,然後用 next 方法把需要的方式給組合在一起

優點與缺點

責任鏈模式提供了很高的擴充彈性,讓我們能夠依序處理需求或請求,同時不需要去修改既有的物件或函式。而缺點就是,不一定所有的請求都會被完全處理到。


上一篇
Facade 外觀模式
下一篇
Command 命令模式
系列文
幫自己搞懂物件導向和設計模式30

尚未有邦友留言

立即登入留言