0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 18 Iteratorパターン:集合体の要素に順次アクセスする

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第18回目です。
今回は、集合体の内部構造を公開することなく、その要素を順番に走査するためのIterator(イテレーター)パターンについて解説します。


Iteratorパターンとは?

Iteratorパターンは、コレクション(リスト、配列など)の内部表現に依存せずに、その要素にアクセスするための振る舞いパターンです。これにより、クライアントはコレクションがどのようにデータを格納しているかを知る必要がなくなり、コードの柔軟性が高まります。

例えるなら、音楽再生リストです。
あなたは、プレイリストがどのように曲を保存しているか(配列か、リンクリストか)を知る必要はありません。ただ「次の曲へ」というボタンを押すだけで、次の曲にアクセスできます。Iteratorパターンは、この「次の要素へ」という共通の操作を提供します。

このパターンの主な目的は以下の通りです。

  • カプセル化: 集合体の内部構造をクライアントから隠蔽する
  • 統一的な走査: 異なる種類の集合体(リスト、ツリーなど)に対して、同じインターフェースで走査できるようにする
  • 複数の走査: 同じ集合体に対して、複数のイテレーターを同時に動作させることができる

Javaでの実装:厳格なインターフェース

Javaは、java.util.Iteratorという標準インターフェースを言語に組み込んでいます。拡張for文(for-each)は、このインターフェースを内部的に利用しています。

このパターンでは、以下のコンポーネントが登場します。

  1. イテレーターインターフェース(Iterator): hasNext()(次に要素があるか)とnext()(次の要素を取得)メソッドを定義します
  2. 具象イテレータークラス(ConcreteIterator): イテレーターインターフェースを実装し、特定の集合体を走査します
  3. 集合体インターフェース(Aggregate): イテレーターを生成するiterator()メソッドを定義します
  4. 具象集合体クラス(ConcreteAggregate): 集合体インターフェースを実装し、具象イテレーターのインスタンスを返します

以下に、簡単なリストを走査する例を示します。

// JavaでのIteratorパターンの実装例

import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.NoSuchElementException;

// 1. 集合体インターフェース
interface Aggregate<T> {
    Iterator<T> iterator();
}

// 2. 具象集合体クラス
class MyList<T> implements Aggregate<T>, Iterable<T> {
    private List<T> items = new ArrayList<>();

    public void add(T item) {
        items.add(item);
    }

    public int size() {
        return items.size();
    }

    @Override
    public Iterator<T> iterator() {
        return new MyListIterator();
    }
    
    // 3. 具象イテレータークラス(内部クラスとして実装)
    private class MyListIterator implements Iterator<T> {
        private int position = 0;

        @Override
        public boolean hasNext() {
            return position < items.size();
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            T item = items.get(position);
            position++;
            return item;
        }
        
        @Override
        public void remove() {
            if (position <= 0) {
                throw new IllegalStateException();
            }
            items.remove(--position);
        }
    }
}

// 使い方
public class IteratorExample {
    public static void main(String[] args) {
        MyList<String> fruits = new MyList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        
        // 明示的なイテレーター使用
        System.out.println("明示的なイテレーター:");
        Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        
        // 拡張for文(内部的にイテレーターを使用)
        System.out.println("\n拡張for文:");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        
        // 複数のイテレーターの同時使用例
        System.out.println("\n複数イテレーター:");
        Iterator<String> iter1 = fruits.iterator();
        Iterator<String> iter2 = fruits.iterator();
        
        while (iter1.hasNext() && iter2.hasNext()) {
            System.out.printf("%s - %s%n", iter1.next(), iter2.next());
        }
    }
}

Javaでは、イテレーターを明示的に作成し、hasNext()next()メソッドを使ってコレクションを走査します。また、Iterableインターフェースを実装することで、拡張for文でも使用できるようになります。


Pythonでの実装:言語機能としてのイテレータ

Pythonは、イテレーターパターンを言語の根本的な部分に組み込んでいます。forループは、オブジェクトの__iter__()メソッド(イテレーターを返す)と、イテレーターの__next__()メソッド(次の要素を返す)を自動的に呼び出します。また、ジェネレーターは、イテレーターを非常に簡潔に作成するための強力な機能です。

以下に、Pythonでイテレーターを実装する例を示します。

# PythonでのIteratorパターンの実装例

# 1. 具象イテレータークラス
class MyListIterator:
    def __init__(self, collection):
        self._collection = collection
        self._position = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self._position >= len(self._collection):
            raise StopIteration
        
        item = self._collection[self._position]
        self._position += 1
        return item

# 2. 具象集合体クラス(__iter__メソッドを持つ)
class MyList:
    def __init__(self, items=None):
        self._items = items or []

    def add(self, item):
        self._items.append(item)

    def __len__(self):
        return len(self._items)

    def __iter__(self):
        return MyListIterator(self._items)

# 使い方
def main():
    fruits = MyList(["Apple", "Banana", "Cherry"])

    # for文が自動的にイテレーターを呼び出す
    print("基本的な反復:")
    for fruit in fruits:
        print(fruit)

    # 明示的なイテレーター使用
    print("\n明示的なイテレーター:")
    iterator = iter(fruits)
    try:
        while True:
            print(next(iterator))
    except StopIteration:
        pass

    # 複数のイテレーターの同時使用
    print("\n複数イテレーター:")
    iter1 = iter(fruits)
    iter2 = iter(fruits)
    
    try:
        while True:
            fruit1 = next(iter1)
            fruit2 = next(iter2)
            print(f"{fruit1} - {fruit2}")
    except StopIteration:
        pass

if __name__ == "__main__":
    main()

この例では、forループがMyListオブジェクトの__iter__を呼び出し、返されたイテレーターオブジェクトの__next__メソッドを繰り返し呼び出しています。

ジェネレーターを使ったよりPythonicな実装

Pythonでは、ジェネレーターを使うことで、さらに簡潔にイテレーターを実装できます。yieldキーワードを持つ関数は、自動的にイテレーターを返します。

# ジェネレーターを使ったIteratorパターンの実装例

class MyList:
    def __init__(self, items=None):
        self._items = items or []
    
    def add(self, item):
        self._items.append(item)
    
    def __iter__(self):
        """ジェネレーターを使った簡潔な実装"""
        for item in self._items:
            yield item
    
    def reverse_iter(self):
        """逆順のイテレーター(ジェネレーター使用)"""
        for i in range(len(self._items) - 1, -1, -1):
            yield self._items[i]

# 使い方
fruits = MyList(["Apple", "Banana", "Cherry"])

print("通常順序:")
for fruit in fruits:
    print(fruit)

print("\n逆順序:")
for fruit in fruits.reverse_iter():
    print(fruit)

# リスト内包表記との組み合わせ
uppercase_fruits = [fruit.upper() for fruit in fruits]
print(f"\n大文字変換: {uppercase_fruits}")

このジェネレーターによる実装は、イテレーターの状態管理(positionなど)をPythonが自動的に行ってくれるため、開発者の負担を大幅に減らします。


実際の使用例:異なるデータ構造での統一的なアクセス

Iteratorパターンの真価は、異なるデータ構造に対して統一的なインターフェースを提供することにあります。

Java例:ツリー構造のイテレーター

// ツリー構造のイテレーター例
class TreeNode<T> {
    T data;
    List<TreeNode<T>> children = new ArrayList<>();
    
    TreeNode(T data) {
        this.data = data;
    }
    
    void addChild(TreeNode<T> child) {
        children.add(child);
    }
    
    // 深さ優先探索のイテレーター
    Iterator<T> depthFirstIterator() {
        return new Iterator<T>() {
            private Stack<TreeNode<T>> stack = new Stack<>();
            {
                stack.push(TreeNode.this);
            }
            
            @Override
            public boolean hasNext() {
                return !stack.isEmpty();
            }
            
            @Override
            public T next() {
                if (!hasNext()) throw new NoSuchElementException();
                
                TreeNode<T> current = stack.pop();
                // 子要素を逆順でスタックに追加(左から右の順序を保つため)
                for (int i = current.children.size() - 1; i >= 0; i--) {
                    stack.push(current.children.get(i));
                }
                return current.data;
            }
        };
    }
}

Python例:ジェネレーターを使ったツリー走査

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.children = []
    
    def add_child(self, child):
        self.children.append(child)
    
    def depth_first_iter(self):
        """深さ優先探索のジェネレーター"""
        yield self.data
        for child in self.children:
            yield from child.depth_first_iter()
    
    def breadth_first_iter(self):
        """幅優先探索のジェネレーター"""
        from collections import deque
        queue = deque([self])
        
        while queue:
            node = queue.popleft()
            yield node.data
            queue.extend(node.children)

パフォーマンスと注意点

メモリ効率性

Iteratorパターンの大きな利点の一つは、**遅延評価(lazy evaluation)**です。特にPythonのジェネレーターは、必要な時にのみ値を生成するため、大きなデータセットでもメモリ効率的に処理できます。

def fibonacci_generator(n):
    """フィボナッチ数列をジェネレーターで生成"""
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

# メモリ効率的:一度に全ての値を生成しない
for num in fibonacci_generator(1000000):
    if num > 1000:
        break
    print(num)

注意点

  1. 並行変更の問題: イテレーション中にコレクションが変更されると、予期しない動作が発生する可能性があります
  2. 一方向性: 基本的なイテレーターは前の要素に戻ることができません
  3. 状態管理: 複数のイテレーターが同じコレクションを走査する際の状態管理に注意が必要です

まとめ:本質は「透過的なアクセス」

特性 Java Python
主な解決策 Iteratorインターフェースの実装 __iter____next__メソッド、またはジェネレーター
設計思想 厳格なインターフェースによるカプセル化 言語機能による透過的なイテレーション
コードの量 比較的多い(明示的なクラス定義が必要) 非常に簡潔(特にジェネレーター)
柔軟性 型安全性が高く、明示的な制御が可能 動的で表現力豊か、遅延評価が容易
パフォーマンス コンパイル時最適化の恩恵 インタープリター言語だが、ジェネレーターは効率的

Iteratorパターンは、両言語で実装のスタイルは異なりますが、**「内部構造を隠蔽し、要素に順次アクセスする」**という本質は共通です。Javaは明示的なインターフェースでこれを実現し、Pythonは言語の強力な機能(イテレータープロトコル、ジェネレーター)を介して、よりシンプルかつ自然に同じ目的を達成します。

どちらの言語でも、Iteratorパターンは大規模なデータ処理、異なるデータ構造への統一的なアクセス、メモリ効率的な処理において重要な役割を果たします。

次回は、オブジェクト間の複雑なやり取りを仲介するMediatorパターンについて解説します。お楽しみに!

次回のテーマは、「Day 19 Mediatorパターン:複雑な相互作用を仲介する」です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?