今回ですが繰り返し処理に利用するIteratorパターンを解説していきます。
登場する役割
- 個体クラス
- 集合体クラス
- 繰り返し処理クラス
役割名 | 役割 |
個体クラス:命名は特になし | Aggregateで扱う個体クラス。 |
集合体クラス:Aggregate | あるクラスの集合体。Iteratorを生成するメソッドを持つ。 |
反復子クラス:Iterator | Aggregateから生成された繰り返し処理の役割を持つクラス。 Aggregateが持つ要素を順番にスキャンして、実行していく。 |
図解でわかるIteratorパターン
先ほどの紹介したクラスだけだとイメージしづらいので、現実世界にあるもので比喩していきましょう!
具体例:買い物
まずは買い物がわかりやすいです。
買い物の場合、商品という単体のクラスが存在し、それらをまとめた買い物カゴが存在します。
最終的に買い物カゴに入れた商品をスキャンして、合計金額分を決済します。
この買い物カゴがAggregateクラスで、決済処理を行うためのスキャン処理がIteratorとなります。
買い物処理は実装する機会が多い機能の一つですから、役立ちますよね。
具体例:本の貸出
先ほどの買い物と非常に似ていますが、図書館の貸出などもいい例だと思います。
本の集合体が本棚ですし、貸出する際にはレジにて申請しないといけません。
少しAggregateのイメージが掴めてきましたか?
具体例:出席
次は人の例として、学校の出席で考えてみましょう!
出席確認で、一人一人の名前を読み上げ、返事が返ってきたら出席している扱いにする。
これは学校で誰もが経験していることかと思います。
この例で考えると
- 個体:生徒
- 集合体:教室
- 繰り返し処理:名前を呼んで出席確認
ということになります。
このように物だけではなく、人においても使えるパターンだとわかります。
具体的なクラスの関係性
先ほど紹介した通り、上記の関係性がイメージしやすいと思います
ここにインターフェースまたは抽象クラスを追加すると下記になります。
インターフェースによってAggregateとIteratorに共通処理を持たせていきます。
具体的なコードイメージ
集合体:Aggregate
interface Aggregate {
/**
* Iteratorを生成
*
* @return ClassRoomIterator
*/
public function iterator(): ClassRoomIterator;
}
/**
* 教室
*/
class ClassRoom implements Aggregate
{
/**
* 生徒の配列
*
* @var Student[]
*/
private array $students;
/**
* 生徒数
*
* @var int
*/
private int $students_count;
public function __construct(?array $students = [])
{
$this->students = $students;
$this->students_count = 0;
}
/**
* 生徒を追加
*
* @param Student $student 生徒
* @return void
*/
public function appendStudent(Student $student): void
{
$this->students_count++;
$num = $this->students_count;
$this->students[$num] = $student;
}
/**
* 生徒数を取得
*
* @return int
*/
public function getLength(): int
{
return count($this->students);
}
/**
* Iteratorを生成
*
* @return ClassRoomIterator
*/
public function iterator(): ClassRoomIterator
{
return new ClassRoomIterator($this);
}
}
Iterator
interface Iterator {
/**
* 次の生徒が存在するか確認
*
* @return bool
*/
public function hasNext(): bool;
/**
* 次の生徒を取得
*
* @return Student
*/
public function next(): Student;
}
/**
* 教室のIterator
*/
class ClassRoomIterator implements Iterator
{
/**
* 教室
*
* @var ClassRoom
*/
private ClassRoom $classRoom;
/**
* 繰り返し回数
*
* @var int
*/
private int $index;
public function __construct(ClassRoom $classRoom)
{
$this->students = $students;
$this->index = 0;
}
/**
* 次の生徒が存在するか確認
*
* @return bool
*/
public function hasNext(): bool
{
$nextNum = $this->index + 1;
if($nextNum <= $this->classRoom->getLength()){
return true;
}
return false;
}
/**
* 次の生徒を取得
*
* @return Student
*/
public function next(): Student
{
$this->index++;
$student = $this->classRoom->getStudentAt($this->index);
return $student;
}
}
実行元
class AttendanceController
{
public function handle()
{
$classRoom = new ClassRoom();
$classRoom->appendStudent(new Student("田中"));
$classRoom->appendStudent(new Student("山田"));
$classRoom->appendStudent(new Student("花子"));
$classRoom->appendStudent(new Student("田村"));
$iterator = $classRoom->iterator(); // iteratorを生成
while ($iterator->hasNext()) {
$student = $iterator->next();
echo $student->name;
}
}
}
なぜIteratorパターンを利用するのか?
下記のコードを見てください
while ($iterator->hasNext()) {
$student = $iterator->next();
echo $student->name;
}
肝となる繰り返し処理の箇所ですが、iteratorのnext()
とhasNext()
メソッドだけを利用しており、ClassRoomの実装に影響しません。
実装と繰り返し処理を切り離せています。
そのため、Studentを管理するクラスをClassRoomからSchoolに変更されても対応可能です。
また他の箇所でも再利用が可能です。
クラスの再利用性に優れています。
関連しているパターン
- Visitorパターン:たくさんの要素が集まっている中を渡り歩きながら、同じ処理を繰り返し適用していく。
- Compositeパターン:再起的な構造をもったパターン。
- Factory Methodパターン:iteratorメソッドがIteratorのインスタンスを生成するときに、Factory Methodパターンを利用することがあります。
コメント