4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Pythonによるデザインパターン実装

Last updated at Posted at 2023-10-11

概要

オブジェクト指向のこころを読んだので、Pythonだとどんな感じで実装できるのかやってみました。

代表的なデザインパターン一覧

デザインパターン名 概要
Facade 複数のサブシステムの複雑なインターフェースを統一された一つのインターフェースにまとめることで、クライアントからの利用を簡単にする。
Adapter 既存のクラスのインターフェースをクライアントが期待する別のインターフェースに変換する。異なるインターフェースをもつクラスを統一的に扱いたい場合に使用する。
Strategy 異なる動作や戦略を表す一連のアルゴリズムを定義し、それぞれをカプセル化して交換可能にする。アルゴリズムを使用するクライアントから独立して、アルゴリズムを変更または選択できるようにする。
Bridge 抽象化と実装を切り離し、それぞれを独立して変更できるようにする。
Abstract Factory 関連するオブジェクトの家族を作成するためのインターフェースを提供し、具体的なクラスのインスタンス化をサブクラスに委ねる。
Decorator オブジェクトに動的に新しい責務を追加する。サブクラスを作成することなく、拡張機能を追加できる。
Observer あるオブジェクトの状態が変更されたときに、そのオブジェクトに依存しているすべてのオブジェクトに通知する。
Template Method アルゴリズムの骨格を定義し、一部のステップをサブクラスでオーバーライドできるようにする。アルゴリズムの構造は変更せずに、特定のステップの実装を変更できる。
Singleton クラスのインスタンスが一つだけであり、そのインスタンスへのグローバルなアクセスポイントを提供する。
Factory Method インスタンス化のロジックをサブクラスに委ねることで、クラスのオブジェクトの生成をサブクラスにするパターン。

デザインパターンの考え方の基本

まず全体の設計を大枠で捉えてから、詳細に進むことが大切です。

  1. シンプルに始めて、段階的に詳細を加えていく
    基本的な概念からスタートして、徐々に詳細を加えていくことで、問題を明確に理解しやすくなります。
    複雑なタスクも管理しやすくなり、段階的な実装やテスト設計も楽になります。

    図形を描く ← 基本的な概念
        ├── 丸を描く ← 詳細を追加
        │   ├── 青い丸を描く ←  さらなる詳細
        │   └── 赤い丸を描く
        │
        └── 四角を描く
            ├── 青い線の四角を描く
            └── 赤い線の四角を描く
    
  2. 変わりやすい要素を特定し、カプセル化する
    システム内で頻繁に変更が起こる箇所を見つけ出し、それをカプセル化することが重要です。
    カプセル化は、未知の閾値や実験的なロジックなど、他の処理と分離したい要素を独立させるのに有効です。これにより、主要な実装がスムーズに進行します。

    上記の例では、図形や色は動的要素です。これらをカプセル化して扱うことで、様々な図形や色を効率的に実装することができるようになります。

  3. オブジェクトの組み合わせやコンポジションを活用する
    クラスの継承は有用なツールですが、過度な使用はコードの複雑性を引き起こす可能性があります。オブジェクトの集約やコンポジションを活用することで、各処理の独立性を確保し、再利用や変更もしやすくなります。
    *コンポジションは一般的に「has-aの関係」とも言われます。

    class DrawShape
        def __init__(self, shape: Shape, color: Color):
            ...
    

    このように実装することで、図形や色を柔軟に取り扱うことができます。

他にも様々な原則や戦略があります。第14章 デザインパターンの原則と戦略 を参照してみてください。この章では、より深くデザインパターンについての知識を深めることができると思います。

パターンを決めるための分析方法

  1. 共通性・可変性分析

    ソフトウェアを設計する際、何が変わりそうで、何が変わりにくいのかを見極めるのは大切です。これを洗い出すのが共通性・可変性分析です。この分析を通じて、再利用性や拡張性を持つ設計を目指します。

    1. まず、共通の特徴や機能をリストアップします。
    2. その中から、一般的な・抽象的な要点を見つけます。
    3. これらの共通の要点をベースにして、具体的なバリエーションや派生を考えます。
    4. どの共通の特徴がどのように関わっているのか、つながりを確認します。

    Strategyパターンでの具体例を記載します。この例では、共通性(リストのソート)と可変性(ソートのアルゴリズム)を明確に分離しています。

    from abc import ABC, abstractmethod
    
    # Strategyインターフェース
    class SortStrategy(ABC):
        @abstractmethod
        def sort(self, dataset: list) -> list:
            pass
    
    # 具体的なStrategy
    class BubbleSort(SortStrategy):
        def sort(self, dataset: list) -> list:
            # バブルソートの実装
            return sorted(dataset) # 例としてPythonの組み込み関数を使用
    
    class QuickSort(SortStrategy):
        def sort(self, dataset: list) -> list:
            # クイックソートの実装
            return sorted(dataset) # 例としてPythonの組み込み関数を使用
    
    # Contextクラス
    class SortContext:
        def __init__(self, strategy: SortStrategy):
            self._strategy = strategy
    
        def execute_sort(self, dataset: list) -> list:
            return self._strategy.sort(dataset)
    
  2. 分析マトリクス

    もう少し具体的に、Excelなどのテーブルを使って分析する方法です。これにより、要件や機能の共通性と可変性を視覚的に捉え、抜け漏れを防ぐことができます。

    1. Excelや他のテーブル作成ツールを使用します。
    2. 最も左の列に基本的な概念を並べます。
    3. 右隣の列には、それらの概念に関連する具体的なケースや詳細を記入します。
      *具体的ケースから概念を抽出する場合、2と3は逆でもいいと思います。
    青い丸を描く 赤い四角を描く
    図形を描く 四角
    色を塗る

    新しい要件やケースが現れた場合、マトリクスに簡単に追加することができます。この方法で、デザインの一貫性と完全性を確保することができます。

Pythonでデザインパターンやってみた

例として、動物の動きや行動をシミュレートするclassを実装してみます。

  • 様々な動物の動きや行動を再現する実装を考えます。
  • その動物が普段どういった行動をするのか、例えば歩行や食事、睡眠などをシミュレートします。
  • ユーザーは特定の動物を選択し、その動物の日常の行動を観察することができます。
    • 例えば、ライオンがどうやって獲物を追いかけるのか、鳥がどうやって空を飛ぶのかなど。
  • 各動物の特有の動きや習性を最もらしく再現することを目指します。

Facadeパターン

複数の処理の複雑さを隠蔽するパターンです。
daily_routine は動物の一日の行動を隠蔽しており、動物がどういった行動をするのかを1つのメソッドで呼び出せるようにしています。

class AnimalActions:
    def walk(self):
        raise NotImplementedError

    def eat(self):
        raise NotImplementedError

    def sleep(self):
        raise NotImplementedError


class AnimalFacade:
    def __init__(self, animal: AnimalActions):
        self.animal = animal

    def daily_routine(self):
        self.animal.walk()
        self.animal.eat()
        self.animal.sleep()


class LionActions(AnimalActions):
    def walk(self):
        print("Lion is walking...")

    def eat(self):
        print("Lion is eating...")

    def sleep(self):
        print("Lion is sleeping...")


if __name__ == "__main__":
    lion = LionActions()
    facade = AnimalFacade(lion)
    facade.daily_routine()

Adapterパターン

異なるインターフェースを持つクラスを統一的に扱えるようにするパターンです。
異なる動物や鳥の動作を1つのインターフェースで扱えるように繋いでくれます。

class AnimalActions:
    def walk(self):
        raise NotImplementedError

    def eat(self):
        raise NotImplementedError

    def sleep(self):
        raise NotImplementedError


class Bird:
    def fly(self):
        print("Bird is flying...")

    def eat(self):
        print("Eating worms")

    def nest(self):
        print("Sleeping in a nest")


class BirdAdapter(AnimalActions):
    def __init__(self, bird: Bird):
        self.bird = bird

    def walk(self):
        self.bird.fly()

    def eat(self):
        self.bird.eat()

    def sleep(self):
        self.bird.nest()


if __name__ == "__main__":
    _bird = Bird()
    adapter = BirdAdapter(_bird)
    adapter.walk()
    adapter.eat()
    adapter.sleep()

Strategyパターン

異なる動作や戦略を一つの手順やクラスにカプセル化するパターンです。
動物ごとの移動方法のようなものを考えるとき、動的にその実装を変更することが容易になります。

from abc import ABC, abstractmethod


class MovementStrategy(ABC):
    @abstractmethod
    def move(self):
        pass


class WalkStrategy(MovementStrategy):
    def move(self):
        print("Walking...")


class SwimStrategy(MovementStrategy):
    def move(self):
        print("Swimming...")


class Animal:
    def __init__(self, movement_strategy: MovementStrategy):
        self.movement_strategy = movement_strategy

    def perform_move(self):
        self.movement_strategy.move()
        self.movement_strategy.move()


if __name__ == "__main__":
    fish = Animal(SwimStrategy())
    fish.perform_move()

Bridgeパターン

複数の流動的要素を分離するパターンです。
このアプローチにより、拡張性を保ちつつ、実装の詳細を隠蔽できるようになります。
今回の例では、動物の種類とその特有の動きを独立に拡張・変更させることが可能になります。
Work以外にEatSleepも同じインターフェイスで実装できるはずです。

from abc import ABC, abstractmethod


class Movement(ABC):
    @abstractmethod
    def move(self):
        pass


class Walk(Movement):
    def move(self):
        print("Walking...")


class Animal(ABC):
    def __init__(self, movement):
        self.movement = movement

    @abstractmethod
    def perform_move(self):
        pass


class Lion(Animal):
    def perform_move(self):
        self.movement.move()


if __name__ == "__main__":
    lion = Lion(Walk())
    lion.perform_move()

Abstract Factoryパターン

関連するオブジェクト群の生成を一貫して行うためのインターフェースを提供するパターンです。
異なる種類のオブジェクト生成の詳細を隠蔽しながらも、拡張性を確保できます。
今回の例では、水中動物と陸上動物、それぞれの動きや食事の方法を独立に追加・変更することができます。
移動や食事以外の行動も、同じファクトリインターフェースを通じて統一的に生成することが可能です。

from abc import ABC, abstractmethod


# Abstract Factory (抽象Factory)
class AnimalFactory(ABC):
    @abstractmethod
    def add_movement(self):
        pass

    @abstractmethod
    def add_eating_behavior(self):
        pass


# 陸上生物のFactory (具体的Factory)
class LandAnimalFactory(AnimalFactory):
    def add_movement(self):
        return Walk()

    def add_eating_behavior(self):
        return HerbivoreEat()


# 水中生物のFactory (具体的なFactory)
class AquaticAnimalFactory(AnimalFactory):
    def add_movement(self):
        return Swim()

    def add_eating_behavior(self):
        return CarnivoreEat()


# Abstract Product (抽象Product)
class Movement(ABC):
    @abstractmethod
    def move(self):
        pass


# 陸上と水中で共通のプロダクト
class EatingBehavior(ABC):
    @abstractmethod
    def eat(self):
        pass


# 陸上生物の具体的なプロダクト
class Walk(Movement):
    def move(self):
        print("Walking on the ground")


class HerbivoreEat(EatingBehavior):
    def eat(self):
        print("Eating plants")


# 水中生物の擬態的なプロダクト
class Swim(Movement):
    def move(self):
        print("Swimming in the water")


class CarnivoreEat(EatingBehavior):
    def eat(self):
        print("Eating other fish")


# Client
def animal_behavior(factory: AnimalFactory):
    movement = factory.add_movement()
    eating_behavior = factory.add_eating_behavior()
    movement.move()
    eating_behavior.eat()


if __name__ == "__main__":
    land_animal = LandAnimalFactory()
    animal_behavior(land_animal)

    aquatic_animal = AquaticAnimalFactory()
    animal_behavior(aquatic_animal)

Decoratorパターン

Decoratorパターンは、既存のオブジェクトに機能を動的に追加するデザインパターンで、コードの変更なしに新機能を組み込めます。このパターンは多数のオブジェクトや行動の組み合わせが存在し、それらの増加が予想される場面で有効です。継承よりもコンポジションを用いて、柔軟に拡張性と再利用性を向上させることができます。

from abc import ABC, abstractmethod


class AnimalActions(ABC):
    @abstractmethod
    def walk(self):
        pass

    @abstractmethod
    def eat(self):
        pass

    @abstractmethod
    def sleep(self):
        pass


class Lion(AnimalActions):
    def walk(self):
        print("Lion is walking...")

    def eat(self):
        print("Lion is eating...")

    def sleep(self):
        print("Lion is sleeping...")


# 新しい機能を追加するための土台
class AnimalDecorator(AnimalActions):
    def __init__(self, animal: AnimalActions):
        self._animal = animal

    def walk(self):
        self._animal.walk()

    def eat(self):
        self._animal.eat()

    def sleep(self):
        self._animal.sleep()


class FastWalkDecorator(AnimalDecorator):
    def walk(self):
        print("Walking really fast!")
        self._animal.walk()  # super().walk() でもOK


if __name__ == "__main__":
    lion = Lion()
    fast_lion = FastWalkDecorator(lion)
    fast_lion.walk()

Observerパターン

あるオブジェクトの状態が変わったときに、それを依存している他のオブジェクトに自動的に通知するためのデザインパターンです。
このアプローチを利用することで、状態の変化を効果的に監視・対応することができます。
今回の例では、動物が行動するたびに、その行動を監視している観察者に通知が行われるという構造を持っています。

from abc import ABC, abstractmethod


class AnimalActions(ABC):
    @abstractmethod
    def walk(self):
        pass

    @abstractmethod
    def eat(self):
        pass

    @abstractmethod
    def sleep(self):
        pass


class Observer(ABC):
    @abstractmethod
    def update(self, action):
        pass


class AnimalWithObservers(AnimalActions):
    def __init__(self):
        self._observers = []

    def add_observers(self, observer: Observer):
        self._observers.append(observer)

    def notify_observers(self, action: str):
        for observer in self._observers:
            observer.update(action)

    def walk(self):
        print("Walking...")
        self.notify_observers("walk")

    def eat(self):
        print("Eating...")
        self.notify_observers("eat")

    def sleep(self):
        print("Sleeping...")
        self.notify_observers("sleep")


class HumanObserver(Observer):
    def update(self, action: str):
        print(f"Human observed: Animal is {action}")


if __name__ == "__main__":
    lion = AnimalWithObservers()
    human = HumanObserver()
    lion.add_observers(human)

    lion.walk()
    lion.eat()
    lion.sleep()

Template Methodパターン

アルゴリズムの骨組みを定義し、いくつかのステップをサブクラスでオーバーライドして詳細を実装するデザインパターンです。
このアプローチにより、アルゴリズムの構造を変更せずに特定のステップの実装を変更することができます。
今回の例では、動物の日常の活動を一連のステップとしてテンプレート化し、その具体的な行動や順序をサブクラスで実装する構造を持っています。

from abc import ABC, abstractmethod


class AnimalRoutine(ABC):
    def daily_routine(self):
        self.get_up()
        self.find_food()
        self.sleep()

    @abstractmethod
    def get_up(self):
        pass

    @abstractmethod
    def find_food(self):
        pass

    @abstractmethod
    def sleep(self):
        pass


class LionRoutine(AnimalRoutine):
    def get_up(self):
        print("Lion wakes up")

    def find_food(self):
        print("Lion hunts")

    def sleep(self):
        print("Lion sleeps")


if __name__ == "__main__":
    lion_routine = LionRoutine()
    lion_routine.daily_routine()

Singletonパターン

Singletonパターンは、そのクラスのインスタンスが一つしか存在しないことを保証し、インスタンスへのアクセスポイントを提供するデザインパターンです。
複数回のインスタンス化を防ぎ、全体のシステム内で単一のインスタンスを共有することができます。
今回の例では、動物園の管理者を唯一の存在として保持し、どこからでも同じ管理者インスタンスにアクセスするための構造を持っています。

class ZooKeeper:
    _instance = None

    def __new__(cls):
        # _instanceが未定義の場合、新しいインスタンスを作成する。
        # super().__new__(cls)は、objectクラスの__new__メソッドを呼び出して、
        # ZooKeeperクラスの新しいインスタンスを作成する。
        # すべてのPythonクラスは暗黙的にobjectクラスを継承するため、
        # super()はobjectクラスを参照する。
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance


if __name__ == "__main__":
    keeper1 = ZooKeeper()
    keeper2 = ZooKeeper()
    print(keeper1 == keeper2)  # True

Factory Methodパターン

オブジェクトの作成をサブクラスに委ねるデザインパターンです。
クラスのインスタンスを作成する責任が具体的なサブクラスに移され、基底クラスはどのクラスのインスタンスが必要かを知る必要がなくなります。
今回の例では、異なる動物を作成するためのメソッドを持つクラスが提供されており、それぞれの動物タイプごとに具体的なファクトリメソッドを実装しています。

from abc import ABC, abstractmethod


class AnimalProvider(ABC):
    @abstractmethod
    def provide(self):
        pass


class Lion:
    def walk(self):
        print("Lion is walking...")

    def eat(self):
        print("Lion is eating...")

    def sleep(self):
        print("Lion is sleeping...")

    def perform_move(self):
        self.walk()
        self.eat()
        self.sleep()


class LionProvider(AnimalProvider):
    def provide(self):
        return Lion()


if __name__ == "__main__":
    lion_provider = LionProvider()
    lion = lion_provider.provide()
    lion.perform_move()

おわりに

いろいろと書いてみましたが、まだ理解不足の部分も多いです。
それぞれのパターンの深い掘り下げや、さらなる詳細は書籍をご覧ください。

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?