1. ritukiii

    No comment

    ritukiii
Changes in body
Source | HTML | Preview
@@ -1,367 +1,552 @@
## 概要
[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();
+
+```