Chain of Responsibility,PHP 責任鏈模式

目錄
簡介
Chain of Responsibility(責任鏈模式)是一種行為型設計模式,用於將一個請求沿著一條「鏈」傳遞,這條「鏈」由多個處理物件組成。每個處理物件決定自己是否要處理該請求,如果不處理就將請求傳給下一個物件。這種設計有助於減少發送者和接收者之間的耦合,讓系統更加靈活和易於擴展。
什麼是 Chain of Responsibility
Chain of Responsibility 用於將一個請求沿著一條「鏈」傳遞,直到有一個物件處理它。想像一下你去咖啡店點了一杯拿鐵,員工會按照一個特定的順序來製作:先磨咖啡豆,然後萃取咖啡,最後加牛奶。每個步驟都是一個「責任」,並且是按照一個特定的順序(也就是「鏈」)來完成的。
在這個模式中,每一個責任都會被分配給一個單獨的物件。這個物件會判斷它是否能夠處理這個請求。如果可以,它就會處理;否則,它會將請求傳遞給鏈中的下一個物件。這樣的好處是,每個處理者只需要關注自己的職責,不需要知道整個鏈的結構。

Chain of Responsibility 類別圖
這個設計模式有助於解耦,讓不同的物件有機會處理請求,而不需要知道其他物件的存在。這樣就易於添加或修改功能,因為你只需要更改相應的「環節」,而不需要修改整個「鏈」。這種設計讓系統更加模組化,每個處理者都可以獨立開發和測試。
舉例來說,如果咖啡店想要推出新口味的拿鐵,比如「焦糖拿鐵」,員工只需要在原有的「製作拿鐵」的鏈上添加一個「加焦糖糖漿」的步驟。這樣,既不需要改變其他步驟,也不會影響其他種類的咖啡。
什麼狀況適合使用 Chain of Responsibility
- 多個處理者:當您有多個物件可以處理同一請求,但您不確定哪個物件應該負責時,可以使用責任鏈模式讓它們依次嘗試處理。
- 動態添加功能:當您想動態地為物件添加處理請求的功能時,責任鏈模式讓您可以靈活地組合不同的處理者。
- 請求分解:當您想將一個大請求拆分成多個小請求,每個請求由不同的物件處理時,責任鏈模式非常適合。
- 降低耦合:當您想降低發送者和接收者之間的耦合度時,責任鏈模式可以讓發送者不需要知道具體的處理者。
- 處理順序重要:當請求的處理順序很重要,且需要按照特定順序執行時,責任鏈模式可以確保順序。
範例教學
假設我們有一個咖啡機,可以添加糖、牛奶,以及拿鐵精華。
原始範例 (未使用 Chain of Responsibility)
function makeCoffee($type, $addMilk = false, $addSugar = false) {
echo "Making a $type coffee.\n";
if ($addMilk) {
echo "Adding milk.\n";
}
if ($addSugar) {
echo "Adding sugar.\n";
}
}
makeCoffee("Espresso", true, false); // Output: Making an Espresso coffee. Adding milk.這種做法會把功能與處理邏輯嵌入在單一函數中,不易擴展。如果未來需要添加新的咖啡類型或新的添加物(如巧克力、焦糖等),就需要修改這個函數,違反了開放封閉原則(Open-Closed Principle)。
改善範例 (使用 Chain of Responsibility)
// 定義一個 CoffeeHandler 介面,它擁有 setNext 和 handle 方法
interface CoffeeHandler {
public function setNext(CoffeeHandler $handler);
public function handle($request);
}
// BaseCoffee 是 CoffeeHandler 介面的基礎實現
class BaseCoffee implements CoffeeHandler {
private $nextHandler; // 下一個處理者
// 設置下一個處理者
public function setNext(CoffeeHandler $handler) {
$this->nextHandler = $handler;
}
// 處理請求,如果有下一個處理者則傳遞給下一個處理者
public function handle($request) {
if ($this->nextHandler) {
$this->nextHandler->handle($request);
}
}
}
// Espresso 處理者繼承 BaseCoffee,專門處理 Espresso 請求
class Espresso extends BaseCoffee {
public function handle($request) {
if ($request == 'Espresso') {
echo "Making an Espresso.\n";
}
parent::handle($request); // 傳遞給下一個處理者(如果有)
}
}
// Milk 處理者繼承 BaseCoffee,添加牛奶
class Milk extends BaseCoffee {
public function handle($request) {
echo "Adding milk.\n";
parent::handle($request); // 傳遞給下一個處理者(如果有)
}
}
// Sugar 處理者繼承 BaseCoffee,添加糖
class Sugar extends BaseCoffee {
public function handle($request) {
echo "Adding sugar.\n";
parent::handle($request); // 傳遞給下一個處理者(如果有)
}
}
// 建立 Chain of Responsibility
$espresso = new Espresso(); // 處理 Espresso 的處理者
$milk = new Milk(); // 處理牛奶的處理者
$sugar = new Sugar(); // 處理糖的處理者
// 設置處理鏈
$espresso->setNext($milk);
$milk->setNext($sugar);
// 發起請求
$espresso->handle('Espresso');這種做法會產生高度解耦、更加靈活、易於擴展和維護的程式。每個處理者都只負責自己的職責,如果需要添加新的處理者(例如添加巧克力),只需要創建一個新的類別並將其加入到鏈中即可,不需要修改現有的程式碼。
更多延伸範例
這個範例會示範如何使用 Chain of Responsibility 模式為咖啡添加不同的成分,如糖、牛奶和巧克力。
// CoffeeHandler 是負責處理咖啡請求的介面
interface CoffeeHandler {
public function setNext(CoffeeHandler $handler);
public function handle($request);
}
// BaseCoffee 是 CoffeeHandler 的基本實現,它將請求轉發給鏈中的下一個處理程序
class BaseCoffee implements CoffeeHandler {
private $nextHandler;
public function setNext(CoffeeHandler $handler) {
$this->nextHandler = $handler;
}
public function handle($request) {
if ($this->nextHandler) {
$this->nextHandler->handle($request);
}
}
}
// Espresso 類別負責製作濃咖啡
class Espresso extends BaseCoffee {
public function handle($request) {
if ($request == 'Espresso') {
echo "Making an Espresso.\n";
}
parent::handle($request);
}
}
// Milk 類別負責添加牛奶
class Milk extends BaseCoffee {
public function handle($request) {
if ($request == 'Milk') {
echo "Adding milk.\n";
}
parent::handle($request);
}
}
// Sugar 類別負責添加糖
class Sugar extends BaseCoffee {
public function handle($request) {
if ($request == 'Sugar') {
echo "Adding sugar.\n";
}
parent::handle($request);
}
}
// Chocolate 類別負責添加巧克力
class Chocolate extends BaseCoffee {
public function handle($request) {
if ($request == 'Chocolate') {
echo "Adding chocolate.\n";
}
parent::handle($request);
}
}
// 使用 Chain of Responsibility
$espresso = new Espresso();
$milk = new Milk();
$sugar = new Sugar();
$chocolate = new Chocolate();
// 設定責任鏈
$espresso->setNext($milk);
$milk->setNext($sugar);
$sugar->setNext($chocolate);
// 製作一杯包含濃咖啡、牛奶、糖和巧克力的咖啡
$espresso->handle('Espresso'); // Output: Making an Espresso.
$milk->handle('Milk'); // Output: Adding milk.
$sugar->handle('Sugar'); // Output: Adding sugar.
$chocolate->handle('Chocolate');// Output: Adding chocolate.這個範例中,我們添加了一個新的處理程序 Chocolate,這樣你就可以為咖啡添加巧克力了。通過這種方式,你可以輕易地擴展責任鏈,添加更多的咖啡成分或其他特性。每個處理程序只需要關注自己的部分,使得程式更容易維護和擴展。
注意:在這個範例中,每個處理者都需要明確指定要處理的請求類型。在實際應用中,你可以根據不同的條件來決定是否處理請求,例如根據請求的屬性、狀態等。
Chain of Responsibility 的優缺點
優點:
- 降低耦合度:發送者和接收者之間不需要直接耦合,發送者不需要知道具體的處理者
- 增強靈活性:可以動態地組合和調整處理鏈,添加或移除處理者都很容易
- 單一職責原則:每個處理者只負責自己的職責,符合單一職責原則
- 開放封閉原則:可以添加新的處理者而不需要修改現有程式碼
缺點:
- 效能考量:如果鏈很長,可能會影響效能,因為請求需要經過多個處理者
- 請求可能不被處理:如果鏈中沒有處理者能夠處理請求,請求可能會被忽略
- 調試困難:當鏈很長時,追蹤請求的處理流程可能會比較困難
- 順序依賴:處理者的順序很重要,如果順序錯誤可能會導致問題
實際應用場景
Chain of Responsibility 模式在實際開發中經常被使用,常見的應用場景包括:
- 請求處理:在 Web 框架中處理 HTTP 請求,例如中間件(Middleware)的處理鏈
- 事件處理:處理事件時,讓多個處理者依次嘗試處理事件
- 權限驗證:多層級的權限驗證,例如先驗證登入狀態,再驗證權限,最後驗證資源存取權限
- 日誌處理:不同級別的日誌由不同的處理者處理(例如錯誤日誌、警告日誌、資訊日誌)
- 表單驗證:多個驗證規則依次檢查表單資料
- 異常處理:不同類型的異常由不同的處理者處理
其他類似的 Design Pattern
在設計模式中,有幾個與 Chain of Responsibility 相似的模式,但它們的用途和應用場景有所不同:
- Command Pattern(命令模式):將請求封裝成物件,讓你可以參數化客戶端。Command Pattern 一般用於單一發送者,而 Chain of Responsibility 用於多個接收者。
- Observer Pattern(觀察者模式):定義物件間的一對多依賴關係,當一個物件狀態改變時,所有依賴它的物件都會收到通知。Observer Pattern 一般用於多個接收者同時處理事件,而 Chain of Responsibility 是依次處理。
主要區別:
- Chain of Responsibility:主要用於降低發送者和多個接收者之間的耦合,請求沿著鏈依次傳遞,直到有處理者處理它
- Command Pattern:將請求封裝成物件,可以延遲執行、記錄、撤銷等,一般用於單一發送者
- Observer Pattern:當事件發生時,所有觀察者都會收到通知,一般用於多個接收者同時處理
總結
Chain of Responsibility 模式是一個非常實用的設計模式,特別適合在需要多個物件依次處理請求的情況下使用。它能夠降低系統的耦合度,提高程式碼的靈活性和可維護性。
記住,責任鏈模式的核心思想是「將請求沿著鏈傳遞,直到有處理者處理它」,這讓我們能夠動態地組合不同的處理者,而不需要修改現有的程式碼。
參考來源
- Design Patterns: Chain of Responsibility
- "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
- ← Previous
Strategy Pattern,PHP 策略模式 - Next →
Template Method Pattern,PHP 模板方法模式



