【GoFデザインパターン】Iteratorパターンを図解で説明

デザインパターン処理を繰り返すIteratorパターン

今回ですが繰り返し処理に利用するIteratorパターンを解説していきます。

目次

登場する役割

  • 個体クラス
  • 集合体クラス
  • 繰り返し処理クラス
役割名役割
個体クラス:命名は特になしAggregateで扱う個体クラス。
集合体クラス:Aggregateあるクラスの集合体。Iteratorを生成するメソッドを持つ。
反復子クラス:IteratorAggregateから生成された繰り返し処理の役割を持つクラス。
Aggregateが持つ要素を順番にスキャンして、実行していく。
Iteratorパターンに登場するクラス
デザインパターン処理を繰り返すIteratorパターン2
デザインパターン処理を繰り返すIteratorパターン2

図解でわかるIteratorパターン

先ほどの紹介したクラスだけだとイメージしづらいので、現実世界にあるもので比喩していきましょう!

具体例:買い物

まずは買い物がわかりやすいです。

Iteratorパターンの具体例①:買い物

買い物の場合、商品という単体のクラスが存在し、それらをまとめた買い物カゴが存在します。

最終的に買い物カゴに入れた商品をスキャンして、合計金額分を決済します。

この買い物カゴがAggregateクラスで、決済処理を行うためのスキャン処理がIteratorとなります。

買い物処理は実装する機会が多い機能の一つですから、役立ちますよね。

具体例:本の貸出

先ほどの買い物と非常に似ていますが、図書館の貸出などもいい例だと思います。

Iteratorパターンの具体例②:図書館

本の集合体が本棚ですし、貸出する際にはレジにて申請しないといけません。

少しAggregateのイメージが掴めてきましたか?

具体例:出席

次は人の例として、学校の出席で考えてみましょう!

Iteratorパターンの具体例②:出席システム

出席確認で、一人一人の名前を読み上げ、返事が返ってきたら出席している扱いにする。

これは学校で誰もが経験していることかと思います。

この例で考えると

  • 個体:生徒
  • 集合体:教室
  • 繰り返し処理:名前を呼んで出席確認

ということになります。

このように物だけではなく、人においても使えるパターンだとわかります。

具体的なクラスの関係性

デザインパターン処理を繰り返すIteratorパターンの具体例③

先ほど紹介した通り、上記の関係性がイメージしやすいと思います

ここにインターフェースまたは抽象クラスを追加すると下記になります。

処理を繰り返すIteratorパターンの具体的なイメージ
処理を繰り返すIteratorパターンの具体的なイメージ

インターフェースによって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パターンを利用することがあります。
ぎゅう
WEBエンジニア
渋谷でWEBエンジニアとして働く。
LaravelとVue.jsをよく取り扱い、誰でも仕様が伝わるコードを書くことを得意とする。
先輩だろうがプルリクにコメントをして、リファクタしまくる仕様伝わるコード書くマン
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次
閉じる