規格模式 Specification Pattern 規格模式,將邏輯條件給抽離出來,獨立成一個模組,而不是在物件內透過邏輯判斷來撰寫複雜的程式碼,簡化物件所需要實踐的邏輯,物件可以套用一個規則,也可以套用多種規則,就像大頭菜本身的價格運算是一種規格,過期後的價格運算又是另一種規格,可以把這個價格運算的邏輯抽離出來獨立成模組。
UML
實作 首當其中我們需要把大頭菜物件給建立出來,具有價格(price)以及數量(count)的記錄、讀取功能,原本會提供計算鈴錢總計(calculatePrice)的功能,但這部分是運算邏輯,所以我們需要把這個功能抽離出來放到規格模組(Specification)當中。
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 class Turnips { protected int $price = 0 ; protected int $count = 0 ; public function __construct (int $price , int $count ) { $this ->price = $price ; $this ->count = $count ; } public function getPrice ( ): int { return $this ->price; } public function getCount ( ): int { return $this ->count; } }
再來定義規格模組(Specification)的介面,這個介面會需要實作大頭菜尚未補足的邏輯,也就是計算鈴錢價格(calculatePrice)的這項功能。
Specification.php
1 2 3 4 5 6 7 8 9 10 interface Specification { public function calculatePrice ( ): int ; }
最後我們有兩種規格模式,分別是正常的大頭菜、壞掉的大頭菜,我們先來實作正常的情況下,大頭菜的總計鈴錢計算規格,這裡提供了可以丟大頭菜集合的方式,無論你有多少顆大頭菜,你通通都丟過來,我會全部加總一起算,就算是一顆也沒問題。
TurnipsSpecification.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 class TurnipsSpecification implements Specification { protected array $turnips ; public function __construct (Turnips ...$turnips ) { $this ->turnips = $turnips ; } public function calculatePrice ( ): int { $total = 0 ; foreach ($this ->turnips as $turnip ) { $total += $turnip ->getPrice () * $turnip ->getCount (); } return $total ; } }
最後是實作壞掉的大頭菜計算模式,這裡也是一樣提供一顆或多顆大頭菜計算,不一樣的點在於因為是壞掉的大頭菜,無法賣出鈴錢,所以無論你丟過來幾顆,我都會回傳給你 0 鈴錢。
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 class SpoiledSpecification implements Specification { protected array $turnips ; public function __construct (Specification ...$turnips ) { $this ->turnips = $turnips ; } public function calculatePrice ( ): int { return 0 ; } }
測試 最後我們要寫個測試,來測試規格模式是不是正確的,主要分別為單顆大頭菜與多顆大頭菜的狀況下,以及正常、壞掉的兩種規格模式的測試。
單顆大頭菜使用正常規格模組,是否能正常計算出鈴錢價格。
多顆大頭菜使用正常規格模組,是否能正常計算出鈴錢價格。
單顆大頭菜使用正常規格模組,再套用壞掉的規格,是否能正常計算出壞掉的鈴錢價格。
多顆大頭菜使用正常規格模組,再套用壞掉的規格,是否能正常計算出壞掉的鈴錢價格。
SpecificationPatternTest.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 class SpecificationPatternTest extends TestCase { public function test_single_turnips ( ) { $turnips = new Turnips (100 , 40 ); $specification = new TurnipsSpecification ($turnips ); $this ->assertEquals (4000 , $specification ->calculatePrice ()); } public function test_multi_turnips ( ) { $turnips_A = new Turnips (100 , 40 ); $turnips_B = new Turnips (90 , 20 ); $turnips_C = new Turnips (110 , 20 ); $specification = new TurnipsSpecification ($turnips_A , $turnips_B , $turnips_C ); $this ->assertEquals (8000 , $specification ->calculatePrice ()); } public function test_single_spoiled ( ) { $turnips = new Turnips (100 , 40 ); $specification = new TurnipsSpecification ($turnips ); $spoiled = new SpoiledSpecification ($specification ); $this ->assertEquals (0 , $spoiled ->calculatePrice ()); } public function test_multi_spoiled ( ) { $turnips_A = new Turnips (100 , 40 ); $turnips_B = new Turnips (90 , 20 ); $turnips_C = new Turnips (110 , 20 ); $specification = new TurnipsSpecification ($turnips_A , $turnips_B , $turnips_C ); $spoiled = new SpoiledSpecification ($specification ); $this ->assertEquals (0 , $spoiled ->calculatePrice ()); } }
最後測試的執行結果會獲得如下:
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 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 ✔ ==> IteratorPatternTest ✔ ✔ ✔ ✔ ==> MediatorPatternTest ✔ ✔ ✔ ==> MementoPatternTest ✔ ==> NullObjectPatternTest ✔ ✔ ✔ ✔ ==> ObserverPatternTest ✔ ==> SpecificationPatternTest ✔ ✔ ✔ ✔ ==> AbstractFactoryTest ✔ ✔ ✔ ✔ ==> BuilderPatternTest ✔ ✔ ✔ ✔ ==> FactoryMethodTest ✔ ✔ ✔ ✔ ==> PoolPatternTest ✔ ✔ ==> PrototypePatternTest ✔ ✔ ==> SimpleFactoryTest ✔ ✔ ✔ ✔ ==> SingletonPatternTest ✔ ==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔ ==> AdapterPatternTest ✔ ✔ ==> BridgePatternTest ✔ ✔ ✔ ==> CompositePatternTest ✔ ✔ ✔ ==> DataMapperTest ✔ ✔ ==> DecoratorPatternTest ✔ ✔ ==> DependencyInjectionTest ✔ ✔ ✔ ==> FacadePatternTest ✔ ==> FluentInterfaceTest ✔ ==> FlyweightPatternTest ✔ ==> ProxyPatternTest ✔ ✔ ==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔ Time: 00:00.036, Memory: 8.00 MB OK (72 tests, 141 assertions)
完整程式碼 設計模式不難,找回快樂而已,以大頭菜為例。
參考文獻