phpで学ぶデザインパターン ~ 第1章 - Iterator ~


## 概要

[java言語で学ぶデザインパターン入門](http://www.amazon.co.jp/%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88Java%E8%A8%80%E8%AA%9E%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E7%B5%90%E5%9F%8E-%E6%B5%A9/dp/4797327030/ref=sr_1_1?ie=UTF8&qid=1454418015&sr=8-1&keywords=java%E8%A8%80%E8%AA%9E%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80)を買って学んだ内容を、PHPに書き直して整理していく予定です。

## Iteratorとは

Iteratorパターンは、`数え上げの抽象化`などと表現されます。
何が便利なのか、最初に普通に配列をforで回す例から、困る事を上げていって、Iteratorで解決してみます。

## 1. 配列をforで回す

この本では、本棚を表す`BookShlef`というクラスに、本を表現する`Book`クラスを追加してく例が紹介されてますので、それにそってやってきます。

```php
<?php

class BookShelf
{
    private $books = [];
    private $index = 0;

    /**
     * 本棚に本を追加
     */
    public function appendBook(Book $book)
    {
        $this->books[] = $book;
    }

    /**
     * 指定した本を返す
     */
    public function getBookAt($index)
    {
        return $this->books[$index];
    }

    /**
     * 本棚の大きさを返す
     */
    public function getLength()
    {
        return count($this->books);
    }
}

class Book
{
    private $name = '';

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Main
{
    public function exec()
    {
        $bookshelf = new BookShelf();
        $bookshelf->appendBook(new Book('なぜ人は浮気を繰り返すのか'));
        $bookshelf->appendBook(new Book('清楚系ビッチの生態'));
        $bookshelf->appendBook(new Book('失敗する恋愛術'));

        for ($i = 0; $i < $bookshelf->getLength(); $i++) {
            echo $bookshelf->getBookAt($i)->getName().PHP_EOL;
        }
    }
}

$main = new Main();
$main->exec();

// => output
// なぜ人は浮気を繰り返すのか
// 清楚系ビッチの生態
// 失敗する恋愛術
```

本棚(BookShelf)にBookを3冊追加し、順に取り出してます。
まぁ良さそうですが、不便な点が2つあります。

1. 今本棚には1次元配列でBookを格納してますが、これを2次元配列にした途端、このままでは動かなくなるので、`getBookAt`を修正する必要がある。
2. 本を追加した順ではなく、最後に追加した順に表示したい場合、Mainクラスの実装を修正する必要がある

## 2. BookShelfにIteratorを実装してみる

```php
<?php

interface OriginalIterator
{
    public function hasNext();
    public function next();
}

class BookShelf implements OriginalIterator
{
    private $books = [];
    private $index = 0;

    /**
     * 本棚に本を追加
     */
    public function appendBook(Book $book)
    {
        $this->books[] = $book;
    }

    public function hasNext()
    {
        if ($this->index < $this->getLength()) {
            return true;
        }

        return false;
    }

    public function next()
    {
        $book = $this->books[$this->index];
        $this->index++;
        return $book;
    }

    /**
     * 本棚の大きさを返す
     */
    public function getLength()
    {
        return count($this->books);
    }
}

class Book
{
    private $name = '';

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Main
{
    public function exec()
    {
        $bookshelf = new BookShelf();
        $bookshelf->appendBook(new Book('なぜ人は浮気を繰り返すのか'));
        $bookshelf->appendBook(new Book('清楚系ビッチの生態'));
        $bookshelf->appendBook(new Book('失敗する恋愛術'));

        while ($bookshelf->hasNext()) {
            echo $bookshelf->next()->getName().PHP_EOL;
        }
    }
}

$main = new Main();
$main->exec();

// => output
// なぜ人は浮気を繰り返すのか
// 清楚系ビッチの生態
// 失敗する恋愛術
```

OriginalIteratorというインターフェイスを作り、BookShelfで実装してみました。
これで、まぁ配列の形が変わってもMainをいじる事はなく、Iteratorをいじればなんとか出来そうです。

ただ、最後に追加した順に走査したい場合、今度はその逆走用のBookShelfを作らないといけないため、この問題はまだ解決出来ないです。面倒です。

じゃあ、最後に、いよいよ本で紹介されてたIteratorパターンを紹介します。

## 3. 集約体から好きなイテレータを使えるようにする

試しに、追加順に表示する順走のイテレータと、最後に追加した順から表示する逆走のイテレータを2つ作って使ってみます。

```php
<?php

interface Aggregater
{
    public function orderIterator();
    public function reverseIterator();
}

interface OriginalIterator
{
    public function hasNext();
    public function next();
}

class BookShelf implements Aggregater
{
    private $books = [];

    /**
     * 本棚に本を追加
     */
    public function appendBook(Book $book)
    {
        $this->books[] = $book;
    }

    public function getBookAt($index)
    {
        return $this->books[$index];
    }

    /**
     * 本棚の大きさを返す
     */
    public function getLength()
    {
        return count($this->books);
    }

    /**
     * 順走イテレータ
     */
    public function orderIterator()
    {
        return new OrderIterator($this);
    }

    /**
     * 順走イテレータ
     */
    public function reverseIterator()
    {
        return new ReverseIterator($this);
    }
}

/**
 * 順走イテレータ
 */

class OrderIterator implements OriginalIterator
{
    private $aggregate;
    private $index = 0;

    public function __construct($aggregate)
    {
        $this->aggregate = $aggregate;
    }

    public function hasNext()
    {
        if ($this->index < $this->aggregate->getLength()) {
            return true;
        }

        return false;
    }

    public function next()
    {
        $book = $this->aggregate->getBookAt($this->index);
        $this->index++;
        return $book;
    }
}

/**
 * 逆走イテレータ
 */
class ReverseIterator implements OriginalIterator
{
    private $aggregate;
    private $index = 0;

    public function __construct($aggregate)
    {
        $this->aggregate = $aggregate;
        $this->index = $this->aggregate->getLength() - 1;
    }

    public function hasNext()
    {
        return ($this->index >= 0);
    }

    public function next()
    {
        $book = $this->aggregate->getBookAt($this->index);
        $this->index--;
        return $book;
    }
}

class Book
{
    private $name = '';

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Main
{
    public function exec()
    {
        $bookshelf = new BookShelf();
        $bookshelf->appendBook(new Book('なぜ人は浮気を繰り返すのか'));
        $bookshelf->appendBook(new Book('清楚系ビッチの生態'));
        $bookshelf->appendBook(new Book('失敗する恋愛術'));

        $orderIterator = $bookshelf->orderIterator();

        echo "順走イテレータ発動!!\n\n";
        while ($orderIterator->hasNext()) {
            echo $orderIterator->next()->getName().PHP_EOL;
        }

        $reverseIterator = $bookshelf->reverseIterator();

        echo "\n\n逆走イテレータ発動!!\n\n";
        while ($reverseIterator->hasNext()) {
            echo $reverseIterator->next()->getName().PHP_EOL;
        }
    }
}

$main = new Main();
$main->exec();

// => output

// 順走イテレータ発動!!

// なぜ人は浮気を繰り返すのか
// 清楚系ビッチの生態
// 失敗する恋愛術


// 逆走イテレータ発動!!

// 失敗する恋愛術
// 清楚系ビッチの生態
// なぜ人は浮気を繰り返すのか
```

このように、集約体とは別でイテレータを実装し、それを使えるようにしておくことで、
呼び出し元の実装を変える事なく、集約体(Aggregater)に対する走査を自由に行うように出来ます

## おまけ

@tadsan さんに教えて頂いた、PHPの組み込みクラスのIteratorを使って、foreachで回せるバージョンも作ってみました。

元のBookShelfは`Countable`でオブジェクトの数を取得可能にし、`ArrayAccess`でイテレータからBookShlefに配列としてアクセス出来るようにしてます。

```php
<?php

interface Aggregater
{
    public function orderIterator();
    public function reverseIterator();
}

class BookShelf implements Aggregater, \ArrayAccess, \Countable
{
    private $books = [];

    public function count()
    {
        return count($this->books);
    }

    /**
     * 本棚に本を追加
     */
    public function appendBook(Book $book)
    {
        $this->books[] = $book;
    }

    public function offsetExists($index)
    {
        return isset($this->books[$index]);
    }

    public function offsetGet($index)
    {
        return $this->books[$index];
    }

    public function offsetSet($index, $book)
    {
        $this->books[$index] = $book;
    }

    public function offsetUnset($index)
    {
        unset($this->books[$index]);
    }


    /**
     * 順走イテレータ
     */
    public function orderIterator()
    {
        return new OrderIterator($this);
    }

    /**
     * 逆走イテレータ
     */
    public function reverseIterator()
    {
        return new ReverseIterator($this);
    }
}

trait OriginalIteratable
{
    private $aggregate;
    private $index = 0;

    public function __construct($aggregate)
    {
        $this->aggregate = $aggregate;
        $this->rewind();
    }

    public function valid()
    {
        return ($this->index >= 0 && ($this->index < count($this->aggregate)));
    }

    public function current()
    {
        return $this->aggregate[$this->index];
    }

    public function key()
    {
        return $this->index;
    }
}

class OrderIterator implements \Iterator
{
    use OriginalIteratable;

    private $aggregate;
    private $index = 0;

    public function rewind()
    {
        $this->index = 0;
    }

    public function next()
    {
        $book = $this->aggregate[$this->index];
        $this->index++;

        return $book;
    }
}

class ReverseIterator implements \Iterator
{
    use OriginalIteratable;

    private $aggregate;
    private $index = 0;

    public function rewind()
    {
        $this->index = count($this->aggregate) - 1;
    }

    public function next()
    {
        $book = $this->aggregate[$this->index];
        $this->index--;

        return $book;
    }
}

class Book
{
    private $name = '';

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Main
{
    public function exec()
    {
        $bookshelf = new BookShelf();
        $bookshelf->appendBook(new Book('なぜ人は浮気を繰り返すのか'));
        $bookshelf->appendBook(new Book('清楚系ビッチの生態'));
        $bookshelf->appendBook(new Book('失敗する恋愛術'));

        $orderIterator = $bookshelf->orderIterator();
        $reverseIterator = $bookshelf->reverseIterator();

        echo "順走イテレータ発動!!\n\n";
        foreach ($orderIterator as $o) {
            echo $o->getName();
            echo "\n\n";
        }

        echo "逆走イテレータ発動!!\n\n";
        foreach ($reverseIterator as $o) {
            echo $o->getName();
            echo "\n\n";
        }
    }
}

$main = new Main();
$main->exec();

```