疊代器模式 Iterator Pattern 疊代器模式,提供一種方法來簡單控制一個集合物件,這段過程並不會暴露該物件的來源或修改它,就有點像是你的背包(Bag)一樣,疊代器(Iterator)可以簡單控制你背包中的大頭菜(Turnips)以及鈴錢(Bells)。
UML
實作 首先我們一樣要先建立大頭菜物件(Turnips),並且賦予一些簡單方法,像是島嶼(Island)、鈴錢價格(Price)及數量(Count),並且提供簡單的取得(get)及賦予(set)方法。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 class Turnips { protected string $island ; protected int $price ; protected int $count ; public function __construct (string $island , int $price , int $count ) { $this ->setIsland ($island ); $this ->setPrice ($price ); $this ->setCount ($count ); } public function setIsland (string $island ) { $this ->island = $island ; } public function setPrice (int $price ) { $this ->price = $price ; } public function setCount (int $count ) { $this ->count = $count ; } public function getIsland ( ): string { return $this ->island; } public function getPrice ( ): int { return $this ->price; } public function getCount ( ): int { return $this ->count; } public function calculatePrice ( ): int { return $this ->getPrice () * $this ->getCount (); } }
再來我們要建立背包(Bag)來負責管理大頭菜這個集合,這裡比較特別的是我們要實作 Countable
以及 Iterator
這兩個介面,讓背包可以直接提供 Count 方法、陣列 Array 相關方法。
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 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 76 77 78 79 80 81 82 83 84 85 86 87 88 use Countable ;use Iterator ;class Bag implements Countable , Iterator { protected array $turnips = []; protected int $currentIndex = 0 ; public function addTurnips (Turnips $turnips ) { $this ->turnips[] = $turnips ; } public function removeTurnips (Turnips $turnipsToRemove ) { foreach ($this ->turnips as $key => $turnip ) { if ($turnip ->getIsland () === $turnipsToRemove ->getIsland ()) { unset ($this ->turnips[$key ]); } } $this ->turnips = array_values ($this ->turnips); } public function count ( ): int { return count ($this ->turnips); } public function current ( ): Turnips { return $this ->turnips[$this ->currentIndex]; } public function key ( ): int { return $this ->currentIndex; } public function next ( ) { $this ->currentIndex++; } public function rewind ( ) { $this ->currentIndex = 0 ; } public function valid ( ): bool { return isset ($this ->turnips[$this ->currentIndex]); } }
額外補充 Countable 繼承 Countable
這個類別可以使用 count()
這個方法,因此需要實作它。
1 2 3 4 class Countable { abstract public count ( void ) : int }
Iterator 繼承 Iterator
這個類別可以使用 current()
、key()
、next()
、rewind()
、valid()
這些方法,因此需要實作它們。
1 2 3 4 5 6 7 8 class Iterator extends Traversable { abstract public current ( void ) : mixed abstract public key ( void ) : scalar abstract public next ( void ) : void abstract public rewind ( void ) : void abstract public valid ( void ) : bool }
測試 IteratorPatternTest.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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 class IteratorPatternTest extends TestCase { public function test_can_iterate_over_bag ( ) { $bag = new Bag (); $bag ->addTurnips (new Turnips ('Island_A' , 100 , 40 )); $bag ->addTurnips (new Turnips ('Island_B' , 90 , 40 )); $bag ->addTurnips (new Turnips ('Island_C' , 80 , 40 )); $islands = []; foreach ($bag as $turnips ) { $islands [] = $turnips ->getIsland (); } $this ->assertSame ( array ( 'Island_A' , 'Island_B' , 'Island_C' ), $islands ); } public function test_can_iterate_over_bag_after_removing_turnips ( ) { $turnipsA = new Turnips ('Island_A' , 100 , 40 ); $turnipsB = new Turnips ('Island_B' , 200 , 10 ); $bag = new Bag (); $bag ->addTurnips ($turnipsA ); $bag ->addTurnips ($turnipsB ); $bag ->removeTurnips ($turnipsA ); $islands = []; foreach ($bag as $turnips ) { $islands [] = $turnips ->getIsland (); } $this ->assertSame ( array ('Island_B' ), $islands ); } public function test_can_add_turnips_to_bag ( ) { $turnips = new Turnips ('Island_A' , 100 , 40 ); $bag = new Bag (); $bag ->addTurnips ($turnips ); $this ->assertCount (1 , $bag ); } public function test_can_remove_turnips_from_bag ( ) { $turnips = new Turnips ('Island_A' , 100 , 40 ); $bag = new Bag (); $bag ->addTurnips ($turnips ); $bag ->removeTurnips ($turnips ); $this ->assertCount (0 , $bag ); } }
最後測試的執行結果會獲得如下:
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 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 ✔ ✔ ✔ ✔ ==> AbstractFactoryTest ✔ ✔ ✔ ✔ ==> BuilderPatternTest ✔ ✔ ✔ ✔ ==> FactoryMethodTest ✔ ✔ ✔ ✔ ==> PoolPatternTest ✔ ✔ ==> PrototypePatternTest ✔ ✔ ==> SimpleFactoryTest ✔ ✔ ✔ ✔ ==> SingletonPatternTest ✔ ==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔ ==> AdapterPatternTest ✔ ✔ ==> BridgePatternTest ✔ ✔ ✔ ==> CompositePatternTest ✔ ✔ ✔ ==> DataMapperTest ✔ ✔ ==> DecoratorPatternTest ✔ ✔ ==> DependencyInjectionTest ✔ ✔ ✔ ==> FacadePatternTest ✔ ==> FluentInterfaceTest ✔ ==> FlyweightPatternTest ✔ ==> ProxyPatternTest ✔ ✔ ==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔ Time: 00:00.117, Memory: 6.00 MB OK (59 tests, 126 assertions)
完整程式碼 設計模式不難,找回快樂而已,以大頭菜為例。
參考文獻