設計模式 - Chain of Responsibility Pattern 職責鏈模式
Intro
假設我們今天要設計一個流程,當處理人處理不來的時候則轉交給下一個處理的人
例如今天產品出了問題
小問題的話CEO就可以唬兩句就解決了
但如果問題大一點的話,可能就要靠經理出來圓個場
但如果今天問題更大的話,
那只好摘贓底下的工程師,讓工程師加班來解決問題
以下我們來實作一下這個處理流程的程式
實作
我們先定義一下出問題的類別,好讓各階層處理的人員可以判斷這個問題有多大條
1 2 3 4 5 6 7 8 9 10 11
| class Trouble { private $Level;
public function __construct(int $Level) { $this->Level = $Level; } }
|
我們希望每個腳色都有一個固定的介面可以去處理問題,所以這邊我們先新增一個抽象類別Handler,讓他制定一個handleTrouble方法,讓外部知道我們只要去使用這個方法就可以解決問題。
另外因為處理不來的事情我們就要丟包給下一個負責的人,所以我們新增一個setNextHandler方法讓外部可以設定處理不來的時候需要交付給誰
1 2 3 4 5 6 7 8 9 10 11 12 13
| abstract class Handler { protected $NextHandler;
abstract public function handleTrouble(Trouble $trouble): string;
public function setNextHandler(Handler $handler) { $this->NextHandler = $handler; } }
|
接下來我們來新增三個腳色,CEO、Manager、Engineer去繼承Handler
讓他們都用handleTroubl去接收問題並處理,然後處理不來的就丟包給下一個人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class CEO extends Handler { public function handleTrouble(Trouble $trouble): string { if ($trouble->Level <= 10) { return 'CEO can handle'; } else { return $this->NextHandler->handleTrouble($trouble); } } }
class Manager extends Handler { public function handleTrouble(Trouble $trouble): string { if ($trouble->Level <= 100) { return 'Manager can handle'; } else { return $this->NextHandler->handleTrouble($trouble); } } }
class Engineer extends Handler { public function handleTrouble(Trouble $trouble): string { return 'Engineer can handle'; } }
|
使用的情況會如下,我們會將CEO、Manager、Engineer職責順序透過setNextHandler去做設定,而今天不管出了甚麼問題,我們就是指向職責最前端的負責人,並且呼叫他的handleTrouble方法,這個問題便可以被解決
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $SmallTrouble = new Trouble(1); $MediumTrouble = new Trouble(11); $BigTrouble = new Trouble(111);
$Engineer = new Engineer(); $Manager = new Manager(); $CEO = new CEO(); $Manager->setNextHandler($Engineer); $CEO->setNextHandler($Manager);
$CEO->handleTrouble($SmallTrouble);
$CEO->handleTrouble($MediumTrouble);
$CEO->handleTrouble($BigTrouble);
|
時機
- 類別對於判斷狀況的職責過多的時候
- 執行程式時無法提前知道會遇到什麼樣的狀況時
- 當必須按照特定順序執行多個處理程序時
- 當我們期望可以在執行時動態更改執行順序時
目的
讓多個物件都有機會處理某一個訊息,以降低發送者與接收者之間的耦合關係。他將接收者的物件串連起來,讓訊息流經其中,直到被處理為止。
類別圖
Plant UML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @startuml
skinparam classAttributeIconSize 0
class Trouble{ {field} + $Level : int }
abstract class Handler{ {field} # $NextHandler : Handler {abstract} + handleTrouble(Trouble $trouble) {method} + setNextHandler(Handler $handler) }
class ConcreteHandlerA{ {field} # $NextHandler : Handler {method} + handleTrouble(Trouble $trouble) }
class ConcreteHandlerB{ {field} # $NextHandler : Handler {method} + handleTrouble(Trouble $trouble) }
package Client <<Rectangle>> { }
ConcreteHandlerA -up-|> Handler ConcreteHandlerB -up-|> Handler ConcreteHandlerA o-up-> Handler : NextHandler ConcreteHandlerB o-up-> Handler : NextHandler Client -right-> Handler
@enduml
|
優點
- 可以控制請求處理的順序
- 發送者及接收者不必直接相關,而串列裡的個別物件也不必知道整條串鏈的構造
- 單一職責原則。可以將調用及執行操作的類別職責分離
- 開放封閉原則。可以在不破壞使用者端程式碼的狀況下新增新的處理順序
缺點
- 某些請求可能會無法被執行
- 效率較低,請求有可能被歷遍之後才得以被處理