「Iterator」パターンとは?サンプルを踏まえてわかりやすく解説!【Java】

  • LINEで送る
「Iterator」パターンとは?サンプルを踏まえてわかりやすく解説!

 本記事ではGoFのデザインパターンのプログラムの振る舞いに関するパターンの一つである「Iterator」パターンを解説します。このパターンを一言で説明するならば、「集合体の要素にアクセスする方法で、集合体とアクセス方法を分離することで、集合体の内部構造を無視してアクセスすることが可能になるパターン」と言えるでしょう。「Iterator」パターンをサンプルを踏まえて解説します。

Iterator」パターンとは

 Iteratorパターンとはコレクション(なんらかの集合体)に対して、内部構造を意識せず各要素に順にアクセスできるようにするデザインパターンです。要素の並び順や格納方法に依存せずに各要素を「1つずつ処理」することが可能です。

 このパターンを例えるなら、レストランのコース料理を注文した時に、客が順番を決めるのではなく、お店側が提供する順番を決めるようなイメージです。客側(クライアント)はどのような順番で食べればおいしく食べられるかを気にする必要はありません。提供する順番は店側の責務です。店側で提供順を決めることで、客は食べることに集中できます。

Iterator

 このようなイメージの仕組みをコードで実装して、集合体への順次処理を簡単にします。

Iterator」パターンの構成要素

 Iteratorパターンは主に下記4つの要素からなります。

要素名役割
Iterator
<<interface>>
要素を1つずつ取得するためのインターフェース
hasNext():次の要素が存在するか
next():次の要素を取得
ConcreteIterator実際の走査ロジックを実装するクラス
Aggregate
<<interface>>
コレクション本体に対して iterator() を提供するインターフェース
ConcreteAggregate要素を保持する実体

Iterator」パターンの書き方

 Iteratorの構成要素をもとに実際のコードで解説します。本記事では商品をショッピングカートに入れる例でコードを実装します。

 カートに商品をいくつか入れて、カート内の商品一覧を表示するコードを実装します。イメージとしては店員がカートから商品を一つずつ取り出してレシートに印字していくイメージです。

iteratorのコードイメージ

 上記の例でIteratorの各要素を当てはめると下記になります。

要素名役割
Iteratorレジの設計書
ConcreteIterator実際のレジのオブジェクト
Iteratorを実装
Aggregate商品カートの設計書
Cart商品カートのオブジェクト
Aggregateを実装

 上記をもとにIteratorを実装します。まずこの商品カートに入れる商品のクラスを下記で定義します。この商品クラスにはコンストラクタとGetterが設定してあり、商品名と商品価格のフィールドを持っています。

public class Item {

    private String name;
    private int price;

    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }
    public int getPrice() {
        return price;
    }
}

Aggregate <<interface>> ※商品カートの設計書

public interface Aggregate<T> {
    Iterator<T> iterator();
}

 要素の集合体からIteratorを返却するメソッドを提供するインターフェースです。全ての集合体のクラスはこのAggregateを実装して、Iteratorを返却できるようにします。

 ジェネリクス型<T>を利用することでどのような型のコレクションでも実装できるように汎用性を持たせています。

ConcreteAggregate ※商品カートのオブジェクト 本例ではCart

 Aggregateを実装してCartのクラスを実装します。

public class Cart implements Aggregate<Item> {

    private List<Item> items;

    public Cart() {
        this.items = new ArrayList<>();
    }

    public List<Item> getItems() {
        return items;
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    public int getSize() {
        return getItems().size();
    }

    public Item getItemAt(int index) {
        return getItems().get(index);
    }

    @Override
    public Iterator<Item> iterator() {
        return new ConcreteIterator(this);
    }
}

 コンストラクタ内でList<Item>の初期化を行なっています。それぞれのメソッドについては以下の通りです。

getItems()itemsのgetter
addItem(Item item)itemsにItemのオブジェクトを追加する
getSize()格納済みのItemの個数をカウントする
getItemAt(int index)indexに指定されたItemを取得する
iterator()Cartに紐づくiteratorを返却する

Iterator <<interface>> ※レジの設計書

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

 コレクションの要素を順に走査するメソッドを提供するインターフェースです。ここでもジェネリクス型<T>を利用することでどのような型のコレクションでも実装できるように汎用性を持たせています。それぞれのメソッドが持つ意味は下記になります。

hasNext()次の要素が存在するかを返却する
next()次の要素を返却する

ConcreteIterator ※実際のレジのオブジェクト

public class ConcreteIterator implements Iterator<Item> {
    private Cart cart;
    private int index = 0;

    public ConcreteIterator(Cart cart) {
        this.cart = cart;
    }

    public boolean hasNext() {
        return index < cart.getSize();
    }

    public Item next() {
        return cart.getItemAt(index++);
    }
}

 ConcreteIteratorはIteratorを実装します。これはCartで利用するIteratorで、Cart内のItemのListを順番に返却します。hasNext()とnext()で順番にItemを返却できるように実装します。

 このクラスはCartの内部構造(List<Item>をフィールドに持っているかどうか)を知らなくてもIteratorで集合体の要素を取得することができます

実際に利用してみる

 では実際に上記のIteratorを使ってレシートに印字してみます。カート内に商品を追加して、その商品をIteratorで走査して標準出力で商品名と商品価格を出力します。

public class IteratorSample {
    public static void main(String[] args) {
        // カート作成
        Cart cart = new Cart();

        // アイテムを追加
        cart.addItem(new Item("オレンジ", 150));
        cart.addItem(new Item("りんご", 100));
        cart.addItem(new Item("バナナ", 120));
        cart.addItem(new Item("いちご", 500));
        

        // イテレータを取得
        Iterator<Item> cartIterator = cart.iterator();

        // カート内の商品を順に表示
        System.out.println("カートの中身:");
        while (cartIterator.hasNext()) {
            Item item = cartIterator.next();
            System.out.println("- " + item.getName() + " (" + item.getPrice() + "円)");
        }
    }
}
>> 出力結果
カートの中身:
- オレンジ (150円)
- りんご (100円)
- バナナ (120円)
- いちご (500円)

 whileを利用し、hasNext条件でcart内のItemを全て走査し、標準出力を行なっています。以上でIteratorの実装は完了になります。

商品棚の集合体を実装してみる

 ここでCartではなく商品棚の内容も同じように出力したい場合どのような実装になるでしょうか。Aggregateインターフェースを実装して商品棚クラス(Shelf)を実装します。

 CartとShelfで同じメソッドが存在するため、Aggregateインターフェースにメソッドを追加して、必ず実装するようにします。

public interface Aggregate<T> {
    Item getItemAt(int index);
    int getSize();
    public Iterator<T> iterator();
}
public class Shelf implements Aggregate<Item> {

    private List<Item> items;

    public Shelf() {
        this.items = new ArrayList<>();
    }

    public List<Item> getItems() {
        return items;
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    @Override
    public Item getItemAt(int index) {
        return getItems().get(index);
    }

    @Override
    public int getSize() {
        return getItems().size();
    }

    @Override
    public Iterator<Item> iterator() {
        return new ConcreteIterator(this);
    }
}

 実装はCartとほとんど一緒です。Cart同様にConcreteIteratorをShelfのIteratorとして返却します。実際に商品棚に商品を入れて出力します。

public class IteratorSample {
    public static void main(String[] args) {
        // カート作成
        Cart cart = new Cart();
        cart.addItem(new Item("オレンジ", 150));
        cart.addItem(new Item("りんご", 100));
        cart.addItem(new Item("バナナ", 120));
        cart.addItem(new Item("いちご", 500));
        Iterator<Item> cartIterator = cart.iterator();

        // カート内の商品を順に表示
        System.out.println("カートの中身:");
        while (cartIterator.hasNext()) {
            Item item = cartIterator.next();
            System.out.println("- " + item.getName() + " (" + item.getPrice() + "円)");
        }

        // 商品棚に商品を追加
        Shelf shelf = new Shelf();
        shelf.addItem(new Item("ぶどう", 500));
        shelf.addItem(new Item("メロン", 1000));
        shelf.addItem(new Item("スイカ", 300));
        shelf.addItem(new Item("キウイ", 100));

        // 商品棚のIteratorを取得
        Iterator<Item> shelfIterator = shelf.iterator();

        // 商品棚内の商品を順に表示
        System.out.println("商品棚の中身:");
        while (shelfIterator.hasNext()) {
            Item item = shelfIterator.next();
            System.out.println("- " + item.getName() + " (" + item.getPrice() + "円)");
        }
    }
}
>> 実行結果
カートの中身:
- オレンジ (150円)
- りんご (100円)
- バナナ (120円)
- いちご (500円)
商品棚の中身:
- ぶどう (500円)
- メロン (1000円)
- スイカ (300円)
- キウイ (100円)

 このように、別の集合体に対しても同じIteratorを利用することができました。走査方法は共通だけど、コレクションに差異がある場合でも問題なく動作します。

Iteratorのメリットとデメリット

メリット

✅メリット説明
✅内部構造に依存しないListやSet、配列などの構造の違いを意識せずに走査できる
✅走査処理を使いまわせる同じIteratorを別のコレクションに使い回すことが可能
✅責務の分離集合体は「データの保持」、Iteratorは「走査処理」のように責務を分けることができる
✅多様な走査方法に対応可能登録順や価格順、昇順降順など、多様な走査方法を提供できる

デメリット

❌デメリット説明
❌実装がやや煩雑小規模なプロジェクトではくクラスが増えるため帰って煩雑になることがある
❌多数のIteratorが必要な場合は肥大化するCartに「登録順」、「価格順」、「名前順」のように複数のIteratorが必要な場合に、管理が煩雑になってしまう。

 Iteratorには上記のメリットとデメリットがあります。これらのメリットとデメリットからIteratorパターンが有効なシチュエーションは下記があります。

  • コレクションの中身を1つずつ処理したいが、内部構造は隠したいとき
  • 複数の集合体(Cart, Shelfなど)に対して同じ処理をしたいとき
  • 走査順序を柔軟に切り替えたいとき
  • 拡張性を意識したアーキテクチャを目指しているとき

まとめ

 Iteratorパターンは、**「集合体の構造を意識せず、要素を順に取り出す」**というシンプルな目的を持ちながら、柔軟性・拡張性・保守性のある設計を可能にします。

 特に、集合体が増えたり、走査方法を切り替えたいときにその真価を発揮します。一方、構造が小規模であれば、Java標準の Iterablefor-each で代替する選択肢も現実的です。

 本記事で利用したサンプルケースのクラス図は下記になります。

Iteratorクラス図

 最後に「Iterator」パターンとは「集合体の要素にアクセスする方法で、集合体とアクセス方法を分離することで、集合体の内部構造を無視してアクセスすることが可能になるパターン」です。うまく使っていきましょう。

 本記事で利用したコードは下記のgit上で公開しております。

https://github.com/tamotech1028/Iterator

最新の投稿

SNSでもご購読できます。

コメント

コメントを残す