範例:歌曲排行(迭代器模式)

Pattern: 迭代器模式

Class Diagram: 歌曲排行


需求一:KTV系統要按照新增到系統的時間,由舊到新,實作歌曲排行

  • 定義系統存取歌曲的類別(解析傳進來的data)
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
{
/**
* @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;
}
}


  • 定義歌曲集合
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
{
/**
* @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類別。


  • 目前遍歷的程式碼
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
{
/**
* @var SongCollection
*/
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
{
/**
* @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注入。


  • 改寫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
{
/**
* @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。


  • 最後改寫遍歷的程式碼
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
{
/**
* @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;
}
}


需求二:按照新增到系統的時間,由新到舊,實作歌曲排行

  • 修改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
{
/**
* @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。

  • 修改遍歷的程式碼,新增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
{
/**
* @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()的能力。

ʕ •ᴥ•ʔ:一個讓我枯坐在翰林茶館兩個小時的模式(汗)。