命令模式 Command Pattern 命令模式,是一種將行為封裝起來裹上美好糖衣的一種模式,並將接收與執行分離出來,就有點像是把大頭菜買賣這件事,如果把買大頭菜、賣大頭菜這兩個動作封裝起來,變成一個命令,分開去執行。
UML
實作 首先我們要先定義命令介面(Command),這個介面需要實作執行(execute)這個方法。
Command.php
1 2 3 4 5 6 7 8 9 10 interface Command { public function execute ( ) ; }
再來我們需要建立命令的執行者(Invoker)、接收者(Receiver),首先執行者會擁有執行命令(Command)的行為,而接收者則是會有特定的功能,像是買入大頭菜、販售大頭菜。
Invoker.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 class Invoker { protected Command $command ; public function setCommand (Command $command ) { $this ->command = $command ; } public function run ( ) { $this ->command->execute (); } }
Receiver.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 60 61 class Receiver { protected int $turnips = 0 ; protected int $bells = 0 ; public function buy (int $price , int $count ) { $total = $price * $count ; if ($this ->bells < $total ) { throw new \InvalidArgumentException ('您的鈴錢不足,無法購買大頭菜。' ); } $this ->turnips += $count ; $this ->bells -= $total ; } public function sell (int $price , int $count ) { if ($this ->turnips < $count ) { throw new \InvalidArgumentException ('您的大頭菜不足,無法販賣大頭菜。' ); } $total = $price * $count ; $this ->turnips -= $count ; $this ->bells += $total ; } public function setBells (int $bells ) { $this ->bells += $bells ; } public function getBells ( ): int { return $this ->bells; } }
Receiver.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 60 61 class Receiver { protected int $turnips = 0 ; protected int $bells = 0 ; public function buy (int $price , int $count ) { $total = $price * $count ; if ($this ->bells < $total ) { throw new \InvalidArgumentException ('您的鈴錢不足,無法購買大頭菜。' ); } $this ->turnips += $count ; $this ->bells -= $total ; } public function sell (int $price , int $count ) { if ($this ->turnips < $count ) { throw new \InvalidArgumentException ('您的大頭菜不足,無法販賣大頭菜。' ); } $total = $price * $count ; $this ->turnips -= $count ; $this ->bells += $total ; } public function setBells (int $bells ) { $this ->bells += $bells ; } public function getBells ( ): int { return $this ->bells; } }
最後要來建立購買(Buy)以及販賣(Sell)的命令(Command),這邊會直接去執行命令所需要做的事情。
BuyCommand.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 class BuyCommand implements Command { protected Receiver $console ; protected int $price ; protected int $count ; public function __construct (Receiver $console , int $price , int $count ) { $this ->console = $console ; $this ->price = $price ; $this ->count = $count ; } public function execute ( ) { $this ->console->buy ($this ->price, $this ->count); } }
SellCommand.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 class SellCommand implements Command { protected Receiver $console ; protected int $price ; protected int $count ; public function __construct (Receiver $console , int $price , int $count ) { $this ->console = $console ; $this ->price = $price ; $this ->count = $count ; } public function execute ( ) { $this ->console->sell ($this ->price, $this ->count); } }
測試 最後要對大頭菜命令模式測試一下,主要會先建立執行與接收,然後先放個 10,000 鈴錢,才有鈴錢買大頭菜,接下來執行一些命令(Command)測試:
購買大頭菜的命令(BuyCommand)。
販賣大頭菜的命令(SellCommand)。
CommandPatternTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class CommandPatternTest extends TestCase { public function test_invocation ( ) { $invoker = new Invoker (); $receiver = new Receiver (); $receiver ->setBells (10000 ); $invoker ->setCommand (new BuyCommand ($receiver , 100 , 40 )); $invoker ->run (); $this ->assertSame (6000 , $receiver ->getBells ()); $invoker ->setCommand (new SellCommand ($receiver , 200 , 40 )); $invoker ->run (); $this ->assertSame (14000 , $receiver ->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 25 26 27 28 29 30 31 PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors. ==> Configuration: ~/php-design-pattern/vendor/codedungeon/phpunit-result-printer/src/phpunit-printer.yml PHPUnit 9.2.6 by Sebastian Bergmann and contributors. ==> ...fResponsibilitiesTest ✔ ✔ ✔ ==> CommandPatternTest ✔ ==> AbstractFactoryTest ✔ ✔ ✔ ✔ ==> BuilderPatternTest ✔ ✔ ✔ ✔ ==> FactoryMethodTest ✔ ✔ ✔ ✔ ==> PoolPatternTest ✔ ✔ ==> PrototypePatternTest ✔ ✔ ==> SimpleFactoryTest ✔ ✔ ✔ ✔ ==> SingletonPatternTest ✔ ==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔ ==> AdapterPatternTest ✔ ✔ ==> BridgePatternTest ✔ ✔ ✔ ==> CompositePatternTest ✔ ✔ ✔ ==> DataMapperTest ✔ ✔ ==> DecoratorPatternTest ✔ ✔ ==> DependencyInjectionTest ✔ ✔ ✔ ==> FacadePatternTest ✔ ==> FluentInterfaceTest ✔ ==> FlyweightPatternTest ✔ ==> ProxyPatternTest ✔ ✔ ==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔ Time: 00:00.062, Memory: 6.00 MB OK (55 tests, 122 assertions)
完整程式碼 設計模式不難,找回快樂而已,以大頭菜為例。
參考文獻