Pattern: 迭代器模式
Class Diagram: 歌曲排行
需求一:KTV系統要按照新增到系統的時間,由舊到新,實作歌曲排行
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
| <?php
namespace App\IteratorPattern\TopSong;
use DateTime;
class Song {
protected $name;
protected $singer;
protected $releaseDate;
public function __construct(array $data) { $this->name = $data['name']; $this->singer = $data['singer']; $this->releaseDate = new DateTime($data['releaseDate']); }
public function getName() { return $this->name; }
public function getSinger() { return $this->singer; }
public function getReleaseDate() { return $this->releaseDate; } }
|
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
| <?php
namespace App\IteratorPattern\TopSong;
class SongCollection {
protected $items = [];
public function __construct(array $originalSongs) { $this->items = $this->generateSongs($originalSongs); }
private function generateSongs($originalSongs) { $result = []; foreach ($originalSongs as $originalSong) { $result[] = new Song($originalSong); }
return $result; }
public function getItems() { return $this->items; }
public function list() { foreach ($this->items as $item) { $result[] = $item->getName(); }
return $result; } }
|
SongCollection就是迭代器模式中的 集合類別 (Aggregate / Collection) 。
不過我們目前還沒實作PHP的IteratorAggregate介面。
而generateSongs()的目的,是為了將不同來源的歌曲資訊,
轉換成系統認識的Song類別。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program {
protected $songCollection;
public function __construct(array $songs) { $this->songCollection = new SongCollection($songs); }
public function list() { return $this->songCollection->list(); } }
|
目前的list()方法,很單純只用到foreach而已。
接著用迭代器模式改寫它。
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
| <?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection; use Iterator;
class SongIterator implements Iterator {
protected $collection;
private $position = 0;
public function __construct(SongCollection $collection) { $this->collection = $collection; }
public function current() { return $this->collection->getItems()[$this->position]; }
public function key() { return $this->position; }
public function next() { $this->position++; }
public function rewind() { $this->position = 0; }
public function valid() { return isset($this->collection->getItems()[$this->position]); } }
|
SongIterator就是迭代器模式中的 迭代器類別 (Iterator) 。
我們實作了PHP的Iterator介面。
必須實作current, key, next, rewind, valid方法,其目的都有寫在PHPDoc中。
而我們在建構式中將剛剛的SongCollection注入。
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
| <?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate; use Traversable;
class SongCollection implements IteratorAggregate {
protected $items = [];
public function __construct(array $dataOfSongs) { $this->items = $this->generateSongs($dataOfSongs); }
private function generateSongs($dataOfSongs) { foreach ($dataOfSongs as $dataOfSong) { $result[] = new Song($dataOfSong); }
return $result; }
public function getItems() { return $this->items; }
public function getIterator(): Traversable { return new SongIterator($this); } }
|
我們實作了PHP的IteratorAggregate介面。
getIterator()方法會將當前的SongCollection注入,並回傳SongIterator。
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
| <?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program {
protected $songCollection;
public function __construct(array $songs) { $this->songCollection = new SongCollection($songs); }
public function list() { $iterator = $this->songCollection->getIterator(); foreach ($iterator as $item) { $result[] = $item->getName(); }
return $result; } }
|
需求二:按照新增到系統的時間,由新到舊,實作歌曲排行
- 修改SongCollection,新增reverse()方法
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
| <?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate; use Traversable;
class SongCollection implements IteratorAggregate {
protected $dataOfSongs;
protected $items = [];
public function __construct(array $dataOfSongs) { $this->dataOfSongs = $dataOfSongs; $this->items = $this->generateSongs($dataOfSongs); }
private function generateSongs($dataOfSongs) { foreach ($dataOfSongs as $dataOfSong) { $result[] = new Song($dataOfSong); }
return $result; }
public function getItems() { return $this->items; }
public function getIterator(): Traversable { return new SongIterator($this); }
public function reverse() { return new static(array_reverse($this->dataOfSongs)); } }
|
這邊的reverse()方法,會將原始資料倒序後,回傳一個新的SongCollection。
- 修改遍歷的程式碼,新增listReverse()方法
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
| <?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program {
protected $songCollection;
public function __construct(array $songs) { $this->songCollection = new SongCollection($songs); }
public function list() { $iterator = $this->songCollection->getIterator(); foreach ($iterator as $item) { $result[] = $item->getName(); }
return $result; }
public function listReverse() { $iterator = $this->songCollection->reverse()->getIterator(); foreach ($iterator as $item) { $result[] = $item->getName(); }
return $result; } }
|
[單一職責原則]
將集合元素 (Song) 、 集合類別 (SongCollection) 及 迭代器 (SongIterator) 的職責分離。
[開放封閉原則]
無論是修改集合元素,或是迭代順序,我們都不會改到所有的程式碼。
[介面隔離原則]
定義出集合類別介面與迭代器介面,讓兩者不會互相影響。
[依賴反轉原則]
透過集合類別介面與迭代器介面,確保有取得迭代器及foreach()的能力。
ʕ •ᴥ•ʔ:一個讓我枯坐在翰林茶館兩個小時的模式(汗)。