本文同步更新於blog
需求一:KTV系统要按照新增到系统的时间,由旧到新,实作歌曲排行
<?php
namespace App\IteratorPattern\TopSong;
use DateTime;
class Song
{
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $singer;
/**
* @var DateTime
*/
protected $releaseDate;
public function __construct(array $data)
{
$this->name = $data['name'];
$this->singer = $data['singer'];
$this->releaseDate = new DateTime($data['releaseDate']);
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getSinger()
{
return $this->singer;
}
/**
* @return DateTime
*/
public function getReleaseDate()
{
return $this->releaseDate;
}
}
<?php
namespace App\IteratorPattern\TopSong;
class SongCollection
{
/**
* @var Song[]
*/
protected $items = [];
public function __construct(array $originalSongs)
{
$this->items = $this->generateSongs($originalSongs);
}
/**
* @param array $originalSongs
* @return Song[]
*/
private function generateSongs($originalSongs)
{
$result = [];
foreach ($originalSongs as $originalSong) {
$result[] = new Song($originalSong);
}
return $result;
}
/**
* @return Song[]
*/
public function getItems()
{
return $this->items;
}
/**
* @return array
*/
public function list()
{
foreach ($this->items as $item) {
$result[] = $item->getName();
}
return $result;
}
}
SongCollection就是迭代器模式中的集合类别 (Aggregate / Collection)。
不过我们目前还没实作PHP的IteratorAggregate介面。
而generateSongs()的目的,是为了将不同来源的歌曲资讯,
转换成系统认识的Song类别。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
/**
* @var SongCollection
*/
protected $songCollection;
public function __construct(array $songs)
{
$this->songCollection = new SongCollection($songs);
}
public function list()
{
return $this->songCollection->list();
}
}
目前的list()方法,很单纯只用到foreach而已。
接着用迭代器模式改写它。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
use Iterator;
class SongIterator implements Iterator
{
/**
* @var SongCollection
*/
protected $collection;
/**
* @var int
*/
private $position = 0;
public function __construct(SongCollection $collection)
{
$this->collection = $collection;
}
/**
* Return the current element
*
* @return Song
*/
public function current()
{
return $this->collection->getItems()[$this->position];
}
/**
* Return the key of the current element
*
* @return int
*/
public function key()
{
return $this->position;
}
/**
* Move forward to next element
*
* @return void
*/
public function next()
{
$this->position++;
}
/**
* Rewind the Iterator to the first element
*
* @return int
*/
public function rewind()
{
$this->position = 0;
}
/**
* Checks if current position is valid
*
* @return void
*/
public function valid()
{
return isset($this->collection->getItems()[$this->position]);
}
}
SongIterator就是迭代器模式中的迭代器类别 (Iterator)。
我们实作了PHP的Iterator介面。
必须实作current, key, next, rewind, valid方法,其目的都有写在PHPDoc中。
而我们在建构式中将刚刚的SongCollection注入。
<?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate;
use Traversable;
class SongCollection implements IteratorAggregate
{
/**
* @var Song[]
*/
protected $items = [];
public function __construct(array $dataOfSongs)
{
$this->items = $this->generateSongs($dataOfSongs);
}
/**
* @param array $dataOfSongs
* @return Song[]
*/
private function generateSongs($dataOfSongs)
{
foreach ($dataOfSongs as $dataOfSong) {
$result[] = new Song($dataOfSong);
}
return $result;
}
/**
* @return Song[]
*/
public function getItems()
{
return $this->items;
}
public function getIterator(): Traversable
{
return new SongIterator($this);
}
}
我们实作了PHP的IteratorAggregate介面。
getIterator()方法会将当前的SongCollection注入,并回传SongIterator。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
/**
* @var SongCollection
*/
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;
}
}
需求二:按照新增到系统的时间,由新到旧,实作歌曲排行
<?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate;
use Traversable;
class SongCollection implements IteratorAggregate
{
/**
* @var array
*/
protected $dataOfSongs;
/**
* @var Song[]
*/
protected $items = [];
public function __construct(array $dataOfSongs)
{
$this->dataOfSongs = $dataOfSongs;
$this->items = $this->generateSongs($dataOfSongs);
}
/**
* @param array $dataOfSongs
* @return Song[]
*/
private function generateSongs($dataOfSongs)
{
foreach ($dataOfSongs as $dataOfSong) {
$result[] = new Song($dataOfSong);
}
return $result;
}
/**
* @return Song[]
*/
public function getItems()
{
return $this->items;
}
public function getIterator(): Traversable
{
return new SongIterator($this);
}
/**
* @return static
*/
public function reverse()
{
return new static(array_reverse($this->dataOfSongs));
}
}
这边的reverse()方法,会将原始资料倒序後,回传一个新的SongCollection。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
/**
* @var SongCollection
*/
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()的能力。
最後附上类别图:
(注:若不熟悉 UML 类别图,可参考UML类别图说明。)
ʕ •ᴥ•ʔ:一个让我枯坐在翰林茶馆两个小时的模式(汗)。
>>: Rails入门:疑难杂症~~ 无情dubug!! 上传Heroku先从资料库开始 PostgreSQL
在昨天我们简单介绍了框架是如何产生配对後,今天我们要来部署 Open-Match 所需要的环境与核心...
for/传统for/高阶函式for回圈比较 const lists = [2, 4, 1, 8, 7...
前言 今天就把刚完成的元件组合成一个页面吧。 框架 (Frame) 我先以桌面显示器为目标, 建立一...
经过前面几篇,我们已经对 flask 的基本专案结构有了认识 知道怎麽建立开发环境和专案 [02] ...
不同的编辑器预设的输入方式都不尽相同,因此同个专案的同个档案使用不同的编辑器修改,可能会因为输入的格...