簡介
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.
這會把功能與處理邏輯嵌入在單一函數中,不易擴展。
改善範例 (使用 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 一般用於多個接收者。