KnowHow

技術的なメモを中心にまとめます。
検索にて調べることができます。

Python基礎 オブジェクト指向4(Iterator) 振る舞いに関するデザインパターン

登録日 :2025/09/23 09:23
カテゴリ :Python基礎

Iteratorとは

コレクションの内部構造を利用者に見せずに、その要素に順番にアクセスする方法を提供するパターン
(コレクション:配列や辞書などのデータをまとめて格納するもの)

ループ処理のインデックスの役割を抽象化し一般化したもの

振る舞いに関するデザインパターン

Iteratorの構成要素

Iterator

  • コレクションを探索するために必要な操作を定義するインターフェース
    (has_next(), next()メソッドなどをもつ)

ConcreteIterator

  • Iteratorで定義したメソッドを実装するクラス
  • ConcretaIterator の実装によって探索の内容を変更することができる
  • 探索を行うコレクションを属性にもつ

Aggregate

  • 探索を行うコレクションを表すインターフェース
  • Iteratorを生成するためのメソッドを定義

ConcreteAggregate

  • Aggregateで定義したメソッドを実装するクラス
  • ConcreteIteratorクラスの新しいインスタンスを返却する

Iteratorメソッド・デメリット

メリット
- 利用者がコレクションの詳細なデータ構造を知る必要がなくなる

  • コレクションの実装と探索のためのアルゴリズムを分離することができる

  • 既存のコードに修正を加えることなく、新しい種類のコレクションやイテレータを追加できる

デメリット
- 単純なコレクションの場合、Iteratorを使用しない方がコードがシンプルになる

Iteratorの使い所

コレクションが複雑なデータ構造をしており、その複雑さを利用者から隠したい場合
(Iteratorのメソッドを呼び出すだけでデータを取得可能)

探索のための方法を複数持たせたい場合
(オープンクローズドの原則に違反することなく探索のためのロジックを追加可能)

サンプルコード

from abc import ABCMeta, abstractmethod
from typing import List


class Patient(object):
    def __init__(self, _id: int, _name: str):
        self.id = _id
        self.name = _name

    def __str__(self):
        return self.name


class IIterator(metaclass=ABCMeta):
    @abstractmethod
    def has_next(self) -> bool:
        pass

    @abstractmethod
    def next(self):
        pass


class Aggregate(metaclass=ABCMeta):
    @abstractmethod
    def get_iterator(self) -> IIterator:
        pass


class WaitingRoom(Aggregate):
    """ConcreteAggregate"""
    def __init__(self):
        self.__patients = []

    def get_patients(self) -> List[Patient]:
        return self.__patients

    def get_count(self) -> int:
        return len(self.__patients)

    def check_in(self, _patient: Patient):
        self.__patients.append(_patient)

    def get_iterator(self) -> IIterator:
        return WaitingRoomIterator(self)


class WaitingRoomIterator(IIterator):
    """ConcreteIterator"""
    def __init__(self, _aggregate: WaitingRoom):
        self.__position = 0
        self.__aggregate = _aggregate

    def has_next(self) -> bool:
        return self.__position < self.__aggregate.get_count()

    def next(self):
        if not self.has_next():
            print("[INFO]:There is no patient.")
            return
        patient = self.__aggregate.get_patients()[self.__position]
        self.__position += 1
        return patient


if __name__ == "__main__":
    waiting_room = WaitingRoom()
    waiting_room.check_in(Patient(1, "Yamada"))
    waiting_room.check_in(Patient(2, "Suzuki"))
    waiting_room.check_in(Patient(3, "Tanaka"))

    iterator = waiting_room.get_iterator()
    # print(iterator.next())
    # print(iterator.next())
    # print(iterator.next())
    # print(iterator.next())
    while iterator.has_next():
        patient = iterator.next()
        print(patient)

    print(iterator.next())

pythonらしく書き直すと、__iter__や__next__を使うことで以下のように書くこともできる。こうすることで、pythonでfor文で使えるなどのメリットがある

class Patient:
    def __init__(self, id, name):
        self.id = id
        self.name = name
    def __repr__(self):
        return f"Patient({self.id}, '{self.name}')"

class WaitingRoom:
    def __init__(self):
        self._patients = []

    def check_in(self, patient):
        self._patients.append(patient)

    def __iter__(self):
        return WaitingRoomIterator(self._patients)

class WaitingRoomIterator:
    def __init__(self, patients):
        self._patients = patients
        self._index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self._index < len(self._patients):
            patient = self._patients[self._index]
            self._index += 1
            return patient
        else:
            raise StopIteration

# サンプルテスト
if __name__ == "__main__":
    waiting_room = WaitingRoom()
    waiting_room.check_in(Patient(1, "Yamada"))
    waiting_room.check_in(Patient(2, "Suzuki"))
    waiting_room.check_in(Patient(3, "Tanaka"))

    for patient in waiting_room:
        print(patient)
  • 補足
    サンプル2では、repr(self)を用いているが、目的は__str__(self)と近い。repr(self)の方が、より詳細な情報をprintで表示できる。