Chain of Responsibility,PHP 責任鏈模式

grey metal chain in close up photography

簡介

Chain of Responsibility 是一種行為型設計模式,用於將一個請求沿著一條「鏈」傳遞,這條「鏈」由多個處理物件組成。每個處理物件決定自己是否要處理該請求,不處理就將請求傳給下一個物件。這有助於減少發送者和接收者之間的耦合。

什麼是 Chain of Responsibility

Chain of Responsibility 用於將一個請求沿著一條「鏈」傳遞,直到有一個物件處理它。想像一下你去咖啡店點了一杯拿鐵,員工會按照一個特定的順序來製作:先磨咖啡豆,然後萃取咖啡,最後加牛奶。每個步驟都是一個「責任」,並且是按照一個特定的順序(也就是「鏈」)來完成的。

在這個模式中,每一個責任都會被分配給一個單獨的物件。這個物件會判斷它是否能夠處理這個請求。如果可以,它就會處理;否則,它會將請求傳遞給鏈中的下一個物件。

chain fo resposibility class diagram
Chain of Resposibility 類別圖

這個設計模式有助於解耦,讓不同的物件有機會處理請求,而不需要知道其他物件的存在。這樣就易於添加或修改功能,因為你只需要更改相應的「環節」,而不需要修改整個「鏈」。

舉例來說,如果咖啡店想要推出新口味的拿鐵,比如「焦糖拿鐵」,員工只需要在原有的「製作拿鐵」的鏈上添加一個「加焦糖糖漿」的步驟。這樣,既不需要改變其他步驟,也不會影響其他種類的咖啡。

什麼狀況適合使用 Chain of Responsibility

  1. 當您有多個物件可以處理同一請求,但您不確定哪個物件應該負責時。
  2. 當您想動態地為物件添加處理請求的功能。
  3. 當您想將一個大請求拆分成多個小請求,每個請求由不同的物件處理。

範例教學

假設我們有一個咖啡機,可以添加糖、牛奶,以及拿鐵精華。

原始範例 (未使用 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.

這會把功能與處理邏輯嵌入在單一函數中,不易擴展。

改善範例 (使用 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,這樣你就可以為咖啡添加巧克力了。通過這種方式,你可以輕易地擴展責任鏈,添加更多的咖啡成分或其他特性。每個處理程序只需要關注自己的部分,使得程式更容易維護和擴展。

其他類似的 Design Pattern

類似功能的設計模式有 Command Pattern 和 Observer Pattern。

主要區別:

  • Chain of Responsibility 主要用於降低發送者和多個接收者之間的耦合。
  • Command Pattern 一般用於單一發送者。
  • Observer Pattern 一般用於多個接收者。

參考來源

  1. Design Patterns: Chain of Responsibility

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *