策略模式 Strategy Pattern 策略模式,可以讓物件在運作時更改其行為或算法,你可以透過切換策略物件來改變計有的功能,你需要實作一個介面來代表這個策略物件,然後在主要類別當中去引入這個策略物件,在需要變更時來切換策略物件,來達成不同狀況下所需要的功能,就像是大頭菜的鈴錢有兩種模式,一種是原本的鈴錢,另一種則是過期後歸零,這個鈴錢運算的模式就可以抽離出來作為策略物件。
UML
實作 首先我們要定義策略的介面,這個介面我們會希望策略物件必須要實作鈴錢運算(calculatePrice)的方法。
Strategy.php
1 2 3 4 5 6 7 8 9 10 interface Strategy { public function calculatePrice (int $price , int $count ): int ; }
再來要實踐大頭菜的策略模式,首先是正常狀況下的大頭菜,會直接拿鈴錢價格、總數相成後即是鈴錢總價,並且將其回傳。
TurnipsStrategy.php
1 2 3 4 5 6 7 8 9 10 11 12 13 class TurnipsStrategy implements Strategy { public function calculatePrice (int $price , int $count ): int { return $price * $count ; } }
至於壞掉的部分,只要大頭菜壞掉就是賣不出去,所以不用進行任何運算,直接回傳 0 鈴錢即可。
SpoliedStrategy.php
1 2 3 4 5 6 7 8 9 10 11 12 13 class SpoliedStrategy implements Strategy { public function calculatePrice (int $price , int $count ): int { return 0 ; } }
最後實作大頭菜物件,我們需要順便把策略物件丟進去,如果在建立大頭菜物件時沒有指定策略物件,那麼預設就給予正常的策略物件,並且提供一個可以臨時切換策略物件的方法,以及計算鈴錢總價的方法,這個計算的方法是透過呼叫策略物件的方法來實踐。
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 class Turnips { protected int $price ; protected int $count ; protected Strategy $strategy ; public function __construct (int $price , int $count , Strategy $strategy = null ) { $this ->price = $price ; $this ->count = $count ; $this ->strategy = empty ($strategy ) ? new TurnipsStrategy () : $strategy ; } public function setStrategy (Strategy $strategy ) { $this ->strategy = $strategy ; } public function calculatePrice ( ): int { return $this ->strategy->calculatePrice ($this ->price, $this ->count); } }
測試 最後我們要測試策略大頭菜是否如預期的可以運行,我們接下來有一項測試分別是建立大頭菜物件,並且給予預設正常的策略物件,正常情況下可以計算出鈴錢,這時候把策略物件替換為壞掉的模式,再重複呼叫方法時,則是獲得 0 鈴錢。
StrategyPatternTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class StrategyPatternTest extends TestCase { public function test_strategy ( ) { $turnips = new Turnips (100 , 40 , new TurnipsStrategy ); $this ->assertEquals (4000 , $turnips ->calculatePrice ()); $turnips ->setStrategy (new SpoliedStrategy ()); $this ->assertEquals (0 , $turnips ->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 38 39 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 ✔ ✔ ✔ ✔ ==> StatePatternTest ✔ ==> StrategyPatternTest ✔ ==> AbstractFactoryTest ✔ ✔ ✔ ✔ ==> BuilderPatternTest ✔ ✔ ✔ ✔ ==> FactoryMethodTest ✔ ✔ ✔ ✔ ==> PoolPatternTest ✔ ✔ ==> PrototypePatternTest ✔ ✔ ==> SimpleFactoryTest ✔ ✔ ✔ ✔ ==> SingletonPatternTest ✔ ==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔ ==> AdapterPatternTest ✔ ✔ ==> BridgePatternTest ✔ ✔ ✔ ==> CompositePatternTest ✔ ✔ ✔ ==> DataMapperTest ✔ ✔ ==> DecoratorPatternTest ✔ ✔ ==> DependencyInjectionTest ✔ ✔ ✔ ==> FacadePatternTest ✔ ==> FluentInterfaceTest ✔ ==> FlyweightPatternTest ✔ ==> ProxyPatternTest ✔ ✔ ==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔ Time: 00:00.084, Memory: 8.00 MB OK (74 tests, 147 assertions)
完整程式碼 設計模式不難,找回快樂而已,以大頭菜為例。
參考文獻