ある日、Iteratorパターンで実装しようとInterfaceを書きかけた時、こんな記事が見つかりました。
PHP でイテレータを使って関数型プログラミングっぽい雰囲気を出す: range 編
どうやらPHPには既にIterator達が実装されており、よしなに使うことができるらしい。。。
Interfaceの中身を見ると、少しAggregateで実装すべきメソッドが多いし、ちょっと面倒だなと思ったところ、こんな記事が見つかりました。
Iteratorをimplementsする奴は情弱。IteratorAggregateを使え
見てみるとサクッと実装できて便利!と思ったので、思わず記事にしちゃいました。
そもそもIteratorパターンとは??
繰り返し処理のためのデザインパターンです。
反復処理を内部の処理によらないようにできるのがメリットですね。
Aggregateが繰り返す対象をもっています。
Aggregateの中身をみて、繰り返しを管理してくれるのがIteratorですね。
とりあえず、自前でIteratorパターンを実装してみる
今回はタスク管理の繰り返しロジックをIteratorパターンで実装してみます。
以下のようなクラス図の実装でします。
Iteratorパターンに関係があるのは、右側の4つのクラスです(点線で囲った部分)。
タスク管理なので、Taskクラスを作りました。nextメソッドの返り値の型指定のためにEntityクラスも設けました。
次にコードの方を見ていきましょう。
詳細な解説は省きますが、呼び出し元(main.php)でTaksListのメソッドを意識することなく繰り返し処理ができているのは嬉しいですね。
Iteratorの実装
まずはAggregateの繰り返しを管理してくれるIteratorの実装です。
hasNextで次の要素があるかを確認して、nextで次の要素にポインタを移します。
Interface IteratorInterface
{
public function next(): Entity;
public function hasNext(): bool;
}
class TaskListIterator implements Iterator
{
private TaskList $task_list;
private int $index = 0;
public function __construct(TaskList $task_list)
{
$this->task_list = $task_list;
}
/**
* 次のタスクがあればtrueを返す。
* @return bool
*/
public function hasNext(): bool
{
return $this->index < $this->task_list->getLength();
}
/**
* 次のタスクがあれば、次のタスクを返す。
* @return Entity
*/
public function next(): Entity
{
if (!$this->hasNext()) {
throw new Exception('次のタスクがありません');
}
$task = $this->task_list->getBookAt($this->index);
$this->index++;
return $task;
}
}
Aggregateの実装
繰り返す要素をもつクラスの実装です。
今回の場合はTaskListがAggregateの具象クラスです。
interface Aggregate
{
public function iterator(): Iterator;
}
class TaskList implements Aggregate
{
private array $task_list = [];
private int $last = 0;
/**
* イテレーターを生成する
* @return Iterator
*/
public function iterator(): Iterator
{
return new TaskListIterator($this);
}
/**
* タスクを追加。追加時にタスクの個数も追加する
* @param Task
* @return Iterator
*/
public function appendTask(Task $task): void
{
$this->task_list[$this->last] = $task;
$this->last++;
}
/**
* タスクの数を取得
* @return int
*/
public function getLength(): int
{
return $this->last;
}
}
Entityの実装
最後はTaskListがもつTaskクラスの実装です。
※ 先ほども記載しましたが、Entityクラスは、nextメソッド返り値の型指定のために用意しました。
abstract class Entity
{
}
class Task extends Entity
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
/**
* タスク名を取得
* @return int
*/
public function getName(): string
{
return $this->name;
}
}
呼び出し元
Iteratorを用いて繰り返し処理を実装しています。
// タスクリストにタスクを追加している
$task_list = new TaskList();
$task_list->appendTask(new Task('タスク1'));
$task_list->appendTask(new Task('タスク2'));
$task_list->appendTask(new Task('タスク3'));
$task_list->appendTask(new Task('タスク4'));
// イテレータを用いてタスク一覧を表示
$task_iterator = $task_list->iterate();
while ($task_iterator->hasNext()) {
$task = $task_iterator->next();
echo $task->getName(). '\n';
}
PHPで用意してくれているIteratorを使って実装する
それでは、PHP用意してくれているIteratorAggregateを使って実装してみましょう。
クラス図はこんな感じです。
※ ArrayIteratorについては、必要最低限記載しています。詳細に知りたい方は、公式ドキュメントを参考にしてみてください。
「クラス図さっきとそんなに変わってないじゃん!」という感じですが、なんと自分で実装するのは、赤枠の部分だけ!
実装すべきメソッドは、getIterator
というメソッドのみです。
コードは先ほどと変わった部分のみ記載しますね。
Aggregate
class TaskList implements IteratorAggregate
{
// コンストラクタやタスクを追加する処理等は省略しています。
public function getIterator(): Traversable
{
return new ArrayIterator($this->task_list);
}
}
呼び出し元
// タスクリストにタスクを追加する(省略)
// イテレータを用いてタスク一覧を表示
$task_iterator = new TaskListIterator($task_list);
foreach($task_iterator as $task){
echo $task->getName();
}
$task_iterator
をforeachで回すと、内部でiteratorが勝手にnext等をしてくれるので、オブジェクトを配列のように扱うことができます。これが便利!
おわり
最後まで読んでいただきありがとうございました!
まだまだPHPも知らないことだらけなので、引き続き色々勉強してみます〜
この記事でこの部分は違うよ等あれば、コメントで優しく教えて下さい!