Python

Python デザインパターンを学ぶ Prototypeパターン

概要

prototypeパターンは、オブジェクトのインスタンスを新たに作成するのではなく、クラス(プロトタイプ)のインスタンスを1つだけ作成し、必要に応じてディープコピーを行う。このパターンは、以下のような場合に特に有効。

  • 初期化のコストが高い
  • 同じタイプのオブジェクトが多数必要だが、設定にコスト(時間・スペース・帯域幅)がかかるいくつか/すべてのプロパティが、すべてのオブジェクトで同じ

以下の例では、ユーザのNoteのプロトタイプのユースケースを考えます。

任意のインデックスのすべてのNoteには、クエリや後処理に時間がかかる可能性のある普遍的なデータがあると仮定する。また各Noteはユーザに属しており、そのユーザに固有のデータが含まれていて、普遍的なデータが入力された後に入力できると仮定する。

NotePrototypeは1つの Index に1つのインスタンス(その Index のブリーダー)しか必要ないと考えられるため、パターンの良い使用例だ。

特定のユーザのためのNoteを作成するには、そのユーザに設定されたIndexに関連付けられた各ブリーダーのクローンを1つ作成するだけでよい。

実装例

NoteFactoryクラスは、実装例のためのクラスでprototypeパターンを非常によく補完する。プロトタイプのすべてのインスタンスはこのクラスに含まれる。これらは、make()メソッドを使ってインターフェイスにすることができる。このクラスは、シングルトンやキャッシュモデルなど、別の方法で実装することができる。

Userクラスは、実装例のためのクラスでprototypeパターンには関係ない。インスタンス化するだけでNoteを生成する。

import copy


class NotePrototype:

    def __init__(self, index):
        self.index = index
        self.note = None
        self.user_id = None
        self._build_general_note()

    def set_user(self, id):
        self.user_id = id
        self._populate_user_note()

    def _build_general_note(self):
        """
        この関数はコストがかかることを想定している。できる限り呼び出さない
        """
        pass

    def _populate_user_note(self):
        """
        データを入力するもので、set_userからのみ呼び出されるべき。
        クローンごとのコストのかかる計算やクエリはすべてここで処理すべき。
        """
        pass

    def clone(self):
        # 独自のIDとプロパティを持つ全く新しいオブジェクトとして複製
        return copy.deepcopy(self)

    def __repr__(self):

        return "<Note: user_id: {}, index: {}>".format(self.user_id,
                                                       self.index)


class NoteFactory():
    _note_breeders = {}

    def __init__(self):
        pass

    def make(self, id, index):
        if index not in NoteFactory._note_breeders:
            NoteFactory._note_breeders[index] = NotePrototype(index)

        clone = NoteFactory._note_breeders[index].clone()
        clone.set_user(id)
        return clone


class User():

    def __init__(self, id, indices, note_factory):

        self.id = id
        self.indices = indices
        self.notes = []
        self.note_factory = note_factory
        self._get_notes()

    def _get_notes(self):

        for index in self.indices:
            note = self.note_factory.make(self.id, index)
            self.notes.append(note)


if __name__ == "__main__":

    factory = NoteFactory()
    user_0 = User(id=0, indices=[0, 1, 2], note_factory=factory)
    user_1 = User(id=1, indices=[1, 2, 3], note_factory=factory)

    print(user_0.notes)
    print(user_1.notes)