Day34. 范例:歌曲排行(迭代器模式)

本文同步更新於blog

需求一:KTV系统要按照新增到系统的时间,由旧到新,实作歌曲排行

  • 定义系统存取歌曲的类别(解析传进来的data)
<?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注入。


  • 改写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;
    }
}


需求二:按照新增到系统的时间,由新到旧,实作歌曲排行

  • 修改SongCollection,新增reverse()方法
<?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()方法
<?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()的能力。


最後附上类别图:
https://ithelp.ithome.com.tw/upload/images/20201019/20111630sG2jhbDOKu.png
(注:若不熟悉 UML 类别图,可参考UML类别图说明。)

ʕ •ᴥ•ʔ:一个让我枯坐在翰林茶馆两个小时的模式(汗)。


<<:  安装10Gbps网路交换器分享

>>:  Rails入门:疑难杂症~~ 无情dubug!! 上传Heroku先从资料库开始 PostgreSQL

Day3 安装 Kubernetes & Open-Match 核心

在昨天我们简单介绍了框架是如何产生配对後,今天我们要来部署 Open-Match 所需要的环境与核心...

Day-30 特集:回圈实例题

for/传统for/高阶函式for回圈比较 const lists = [2, 4, 1, 8, 7...

Day 4 - 原型 (3): 主页的元件组合

前言 今天就把刚完成的元件组合成一个页面吧。 框架 (Frame) 我先以桌面显示器为目标, 建立一...

[10] [Flask 快速上手笔记] 09.心得

经过前面几篇,我们已经对 flask 的基本专案结构有了认识 知道怎麽建立开发环境和专案 [02] ...

16 - EditorConfig - 配置输入方式

不同的编辑器预设的输入方式都不尽相同,因此同个专案的同个档案使用不同的编辑器修改,可能会因为输入的格...