💠

【デザインパターン】Iterator

2023/03/05に公開約3,500字

イテレーター(Iterator)とは

イテレーターとは、オブジェクトの要素に順番にアクセスするための処理方法や手段を提供するオブジェクトやパターンのことや。
イテレーターを使うことで、配列やオブジェクトなどの要素に順番にアクセスすることができるで。
コレクションの内部構造を利用者に見せずに、その要素に順番にアクセスする方法を提供する(デザイン)パターンということやな。
イテレーターと聞いて想像する、forループのiのパターンを抽象化したものやで。

イテレーターの構成要素

イテレーターパターンは、以下の構成要素でつくられるで

  • Iterator: コレクション探索に必要な操作を定義するインターフェース
  • ConcreteIterator:Iteratorで定義したメソッドを実装するクラス
    • 探索の内容を変更することができる
    • 探索を行うコレクションを内部フィールドに持つ
  • Aggregate:探索を行うコレクションを表すインターフェース
    • Iteratorを生成するメソッドを定義
  • ConcreteAggregate:Aggregateで定義したメソッドを実装するクラス
    • ConcreteIteratorクラスの新しいインスタンスを返却

コレクションの操作とデータを別クラスとして切り出してるんやな
こうすることで利用者はコレクションの詳細なデータ構造を知る必要がなくなるで。
またコレクションの実装と探索のためのアルゴリズムを分離することができるから、コード修正や解読が楽になるというメリットがあるな
どういう場合に使えばいいかというと、コレクションのデータ構造が複雑な場合や、探索の方法を複数持たせない場合などに変更に強い設計になることが考えられるんや。すなわちオープンクローズドの原則にも則すようなコードにもできるな。

interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}

interface Aggregate<T> {
  createIterator(): Iterator<T>;
}

class ConcreteIterator<T> implements Iterator<T> {
  private cursor = 0;

  constructor(private readonly aggregate: T[]) {}

  hasNext(): boolean {
    return this.cursor < this.aggregate.length;
  }

  next(): T {
    return this.aggregate[this.cursor++];
  }
}

class ConcreteAggregate<T> implements Aggregate<T> {
  constructor(private readonly data: T[]) {}

  createIterator(): Iterator<T> {
    return new ConcreteIterator(this.data);
  }
}

// Example usage
const data = [1, 2, 3, 4];
const aggregate = new ConcreteAggregate(data);
const iterator = aggregate.createIterator();

while (iterator.hasNext()) {
  console.log(iterator.next());
}

前述したのはイテレーターパターンのコードやで。
配列 [1, 2, 3, 4]ConcreteAggregate オブジェクトに渡されて、イテレーターが生成されるねん。その後、while ループ内で、イテレーターの hasNext メソッドが true を返す限り、イテレーターの next メソッドが呼び出されて、配列内の要素が順番にコンソールに出力されるんや。
出力結果はこうなるで。

1
2
3
4

実装例

それでは具体的な実装例を見ていくやで
お客さんが、待合室に溜まっていっていくようなシステムに当てはめてみよう
構成要素に当てはめると以下のようになるな

  • Iterator:
  • ConcreteIterator:waitingRoomIterator
  • Aggregate:
  • ConcreteAggregate:WaitingRoom
export {}

class Customer {
  constructor(public id: number, public name: string) {}
}

interface Iterator {
  hasNext(): boolean;
  next();
}

interface Aggregate {
  getIterator(): Iterator;
}

class WaitingRoom implements Aggregate {
  private customers: Customer[] = [];

  getCustomers(): Customer[] {
    return this.customers;
  }

  getCount(): number {
    return this.customers.length;
  }

  checkIn(customer: Customer) {
    this.customers.push(customer);
  }

  checkOut(id: number) {
    this.customers = this.customers.filter((customer) => customer.id !== id);
    console.log(`${id}番の顧客を退室しました。`);
  }

  getIterator(): Iterator {
    return new waitingRoomIterator(this);
  }
}

class waitingRoomIterator implements Iterator {
  private position: number = 0;

  constructor(private aggregate: WaitingRoom) { }

  hasNext(): boolean {
    return this.position < this.aggregate.getCount();
  }

  next() {
    if (!this.hasNext()) {
      return console.log("対象がありません");
    }

    const customer = this.aggregate.getCustomers()[this.position];
    this.position++;
    return customer;
  }
}

const room = new WaitingRoom();
room.checkIn(new Customer(1, "山田太郎"));
room.checkIn(new Customer(2, "田中次郎"));
room.checkIn(new Customer(3, "佐藤花子"));
room.checkOut(2);
const iterator = room.getIterator();
console.log("--- 顧客一覧 ---");
while (iterator.hasNext()) {
  const customer = iterator.next();
  console.log(customer.id, customer.name);
}

/* 実行結果

2番の顧客を退室しました。
--- 顧客一覧 ---
1 山田太郎
3 佐藤花子

*/

コレクションの挙動に関するクラスと、コレクションの探索のためのクラスが分かれて実装されていることがみてとれるな。

Discussion

ログインするとコメントできます