責任鏈模式 Chain of Responsibilities
責任鏈模式,有一系列的命令物件及處理物件,常見於需要被連續處理的地方上,舉例來說,假設今天收購箱、商店收購大頭菜時,多了一些條件,你必須先把大頭菜拿去收購箱收購,並且收購箱子會有鈴錢價格打 8 折的情形,剩下有多餘的大頭菜才能拿去給商店收購。
UML
實作
首先我們要先把背包(Bag)、大頭菜(Turnips)這兩個東西給實作出來,先從大頭菜開始,一樣建立簡單的鈴錢、數量以及獲取賦予方法。
Turnips.php
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
class Turnips {
protected int $price;
protected int $count;
public function __construct(int $price, int $count) { $this->setPrice($price); $this->setCount($count); }
public function setPrice(int $price) { $this->price = $price; }
public function setCount(int $count) { $this->count = $count; }
public function getPrice(): int { return $this->price; }
public function getCount(): int { return $this->count; } }
|
再來要建立背包(Bag)這個物件,裡面主要會放置兩個主要的物件,分別是大頭菜($turnips: Turnips)以及鈴錢($bells: int),並且擁有簡單的取得(get)及賦予(set)方法。
Bag.php
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 35 36 37 38 39 40 41 42 43 44 45 46 47
|
class Bag {
protected Turnips $turnips;
protected int $bells = 0;
public function setTurnips(Turnips $turnips) { $this->turnips = $turnips; }
public function setBells(int $bells) { $this->bells = $bells; }
public function getTurnips(): Turnips { return $this->turnips; }
public function getBells(): int { return $this->bells; } }
|
再來我們要建立主要的處理物件(Handler),這個處理物件是抽象介面,主要先實作出上層的處理物件是什麼,其次需要繼承的子類別需要實作賣大頭菜的方法。
Handler.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
abstract class Handler {
protected $upper;
public function setUpperHandler(Handler $upper) { $this->upper = $upper; }
abstract public function sellTurnips(Bag $bag): Bag; }
|
最後我們要建立收購箱(Box)的處理物件,以及商店(Store)的處理物件,收購箱最多只能賣 20 顆大頭菜,並且收購價格都會打 8 折,如果背包裡有剩餘的大頭菜,那麼收購箱就會去呼叫上層處理物件去處理剩下的大頭菜,而在這邊收購箱(Box)的上層物件就會是商店(Store)。
BoxHandler.php
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
|
class BoxHandler extends Handler {
public function sellTurnips(Bag $bag): Bag { $bells = function(int $oldBells, int $price, int $count) { $_bells = ($price * $count) * 0.8; $_bells += $oldBells; return $_bells; };
if ($bag->getTurnips()->getCount() >= 20) { $newBells = $bells($bag->getBells(), $bag->getTurnips()->getPrice(), 20); $bag->setBells($newBells); $bag->getTurnips()->setCount($bag->getTurnips()->getCount() - 20);
return $this->upper->sellTurnips($bag); }
$newBells = $bells($bag->getBells(), $bag->getTurnips()->getPrice(), $bag->getTurnips()->getCount()); $bag->setBells($newBells); $bag->getTurnips()->setCount(0);
return $bag; } }
|
StoreHandler.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
class StoreHandler extends Handler {
public function sellTurnips(Bag $bag): Bag { $bells = $bag->getTurnips()->getPrice() * $bag->getTurnips()->getCount(); $bells += $bag->getBells(); $bag->getTurnips()->setCount(0); $bag->setBells($bells);
return $bag; } }
|
測試
最後我們要來進行一連串的測試,來測試大頭菜責任鏈模式是否可以正常運作,所以會有幾些測試項目需要執行:
- 測試一次賣 40 顆大頭菜,是否會有 20 顆大頭菜在商店被賣出。
- 測試一次賣 20 顆大頭菜,是否全部的大頭菜都只會在收購箱被賣出。
- 測試賣兩次大頭菜,所賣出的鈴錢是否正確。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
class ChainOfResponsibilitiesTest extends TestCase {
protected $bag;
protected $handler;
protected function setUp(): void { parent::setUp();
$this->bag = new Bag();
$this->handler = new BoxHandler(); $storeHandler = new StoreHandler(); $this->handler->setUpperHandler($storeHandler); }
public function test_sell_turnips_to_store() { $this->bag->setTurnips(new Turnips(100, 40)); $this->bag = $this->handler->sellTurnips($this->bag); $this->assertEquals(3600, $this->bag->getBells()); }
public function test_sell_turnips_to_box() { $this->bag->setTurnips(new Turnips(80, 20)); $this->bag = $this->handler->sellTurnips($this->bag); $this->assertEquals(1280, $this->bag->getBells()); }
public function test_sell_turnips_to_box_and_store() { $this->bag->setTurnips(new Turnips(80, 20)); $this->bag = $this->handler->sellTurnips($this->bag); $this->assertEquals(1280, $this->bag->getBells());
$this->bag->setTurnips(new Turnips(100, 60)); $this->bag = $this->handler->sellTurnips($this->bag); $this->assertEquals(6880, $this->bag->getBells()); } }
|
最後測試的執行結果會獲得如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ==> ...fResponsibilitiesTest ✔ ✔ ✔ ==> AbstractFactoryTest ✔ ✔ ✔ ✔ ==> BuilderPatternTest ✔ ✔ ✔ ✔ ==> FactoryMethodTest ✔ ✔ ✔ ✔ ==> PoolPatternTest ✔ ✔ ==> PrototypePatternTest ✔ ✔ ==> SimpleFactoryTest ✔ ✔ ✔ ✔ ==> SingletonPatternTest ✔ ==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔ ==> AdapterPatternTest ✔ ✔ ==> BridgePatternTest ✔ ✔ ✔ ==> CompositePatternTest ✔ ✔ ✔ ==> DataMapperTest ✔ ✔ ==> DecoratorPatternTest ✔ ✔ ==> DependencyInjectionTest ✔ ✔ ✔ ==> FacadePatternTest ✔ ==> FluentInterfaceTest ✔ ==> FlyweightPatternTest ✔ ==> ProxyPatternTest ✔ ✔ ==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔
Time: 00:00.066, Memory: 6.00 MB
OK (54 tests, 120 assertions)
|
完整程式碼
設計模式不難,找回快樂而已,以大頭菜為例。
參考文獻