LoginSignup
18
31

(python) デザインパターンとアンチパターン(Strategy編)

Last updated at Posted at 2023-09-24

目的

これはHead Firstデザインパターン 第2版 ―頭とからだで覚えるデザインパターンの基本に出てくる内容について、
サンプルコードをPythonで補足した記事になります。(本書ではJavaで書かれているため)

本書との併用を前提としています。

Strategy

Strategyパターン主に以下の3つのコンポーネントで構成されます。

  • Context:
    現在のStrategyオブジェクトを保持するクラス。クライアントはこのクラスを通じてStrategyを利用します。
  • Strategy:
    すべての具体的なStrategyが実装するインターフェースや抽象クラス。
  • ConcreteStrategy:
    Strategyインターフェースを実装する各具体的なクラス。これらは具体的なアルゴリズムや方法を提供します。

今回はStrategyパターンに沿わないアンチパターンコードと沿ったデザインパターンコードをそれぞれ見ていきます。

アンチパターン

まず、Strategyではないパターンを示します。
以下にカモを表示し、泳がせたり鳴かせたり飛ばしたりするコードがあります。

from abc import ABC, abstractmethod

# スーパークラスの定義
class Duck(ABC):
    # カモが泳ぐ振る舞いの実装
    def swim(self):
        print("足をバタつかせて泳ぐよ!")

    # カモが鳴く振る舞いの実装
    def quack(self):
        print("ガーガーと鳴くよ!")

    # カモが飛ぶ振る舞いの実装
    def fly(self):
        print("羽ばたいて飛ぶよ!")

    @abstractmethod
    def display(self):
        # 抽象メソッド
        raise NotImplementedError


# カモサブクラス(マガモ)
class MallardDuck(Duck):
    def display(self):
        print("マガモを表示するよ!")

# カモサブクラス(赤毛ガモ)
class RedheadDuck(Duck):
    def display(self):
        print("赤毛ガモを表示するよ!")


# マガモを使う例
mallard_duck_model = MallardDuck()
mallard_duck_model.display()
mallard_duck_model.quack()
mallard_duck_model.fly()

# 赤毛ガモを使う例
red_head_duck_model = RedheadDuck()
red_head_duck_model.display()
red_head_duck_model.quack()
red_head_duck_model.fly()

ここで、ではカモは"足をバタつかせて"泳ぎ、"ガーガー"と鳴き、"羽ばたいて"飛ぶよう定義しています。
表示するカモの種類がマガモと赤毛ガモであればこれらの動作で問題はありませんでした。

要件の追加

しかし、ここで新たな要件の追加が入りました。

新たにゴム製のカモが必要になりました。
この際、ゴム製カモは以下の挙動が必要になります。
・ゴム製カモはジェット機構をつかって飛びます。

ゴム製カモのためにコードを修正

要件の追加に従って、ここでは以下の手順でコードの修正を行います。

  • Duckクラスに新しいふるまい(ジェット機構で飛ぶ)を追加
  • ゴム製カモのサブクラスを追加

※変更点のみ記載

from abc import ABC, abstractmethod

# スーパークラスの定義
class Duck(ABC):
    (省略)

    # カモがジェット機構で飛ぶ振る舞いの実装
    def jet_fly(self):
        print("ジェット機構で飛ぶよ!")


    (省略)

# カモサブクラス(ゴム製カモ)
class RubberDuck(Duck):
    def display(self):
        print("ゴム製ガモを表示するよ!")

上記の修正で以下のようにゴム製カモの挙動を使うことができます。
しかし、既存のコードを改修したことにより既存のカモたちの挙動にも影響が出てしまいます。

# ゴム製カモを使う
rubber_duck_model = RubberDuck()
rubber_duck_model.jet_fly()  # ジェット機構で飛ぶよ!

# マガモを使う
mallard_duck_model = MallardDuck()
mallard_duck_model.jet_fly()  # マガモもジェット機構で飛べてしまう!

ダメな部分
"Duck"クラスを直接変更してしまっています。
そのため既存のコードの他の部分にまで影響が出てしまっています。

これではStrategyパターンの目的とは異なってしまっています。

デザインパターン

次に、Strategyパターンを示します。
結論、アンチパターンのコードを以下のように改修します。

飛び方の振る舞いを独立したインターフェース(または抽象クラス)として定義し、それを実装する具体的なクラスを作成する必要があります。そして、Duckクラスはこれらの振る舞いのインターフェースを持つように変更します。

具体的に以下の手順でコードを変更し、コンポーネント化します。

  1. Strategyコンポーネント
  • 飛ぶ振る舞いと鳴く振る舞いのインターフェースを定義します。
    • FlyBehavior インターフェースがこれに該当します。
  1. ConcreteStrategyコンポーネント
  • これらの振る舞いの具体的な実装を提供するクラスを作成します。
    • FlyWithWings が該当します。
  1. Contextコンポーネント
  • これらの振る舞いを保持するクラスを追加します。
    • Duck クラスがContextに該当します。
  • サブクラスでこれらの振る舞いの具体的な実装を設定します。
    • Duck のサブクラス(MallardDuck、RedheadDuck)が該当します。

各コンポーネントについては以下を参照してください。

・Context: 現在のStrategyオブジェクトを保持するクラス。クライアントはこのクラスを通じてStrategyを利用します。
・Strategy: すべての具体的なStrategyが実装するインターフェースや抽象クラス。
・ConcreteStrategy: Strategyインターフェースを実装する各具体的なクラス。これらは具体的なアルゴリズムや方法を提供します。

修正したコードが以下になります。

from abc import ABC, abstractmethod

# [Strategyコンポーネント]
# 飛ぶ振る舞いをするためのインターフェース
class FlyBehavior(ABC):
    @abstractmethod
    def fly(self):
        pass

# [ConcreteStrategyコンポーネント]
# 飛ぶ振る舞いの具体的な実装(羽ばたく場合)
class FlyWithWings(FlyBehavior):
    def fly(self):
        print("羽ばたいて飛ぶよ!")

# [Contextコンポーネント]
# スーパークラスの定義
class Duck(ABC):
    # fly_behaviorとquack_behaviorはFlyBehaviorクラスとQuackBehaviorクラスのインスタンスであることを示します。
    # すべてのカモサブクラスはこの2つの変数を継承します。
    fly_behavior: FlyBehavior = None

    # カモが泳ぐ振る舞いの実装
    def swim(self):
        print("足をバタつかせて泳ぐよ!")

    # カモが鳴く振る舞いの実装
    def quack(self):
        print("ガーガーと鳴くよ!")

    @abstractmethod
    def display(self):
        # 抽象メソッド
        raise NotImplementedError

    # perform_fly()メソッドで、Duckクラスに指定されたfly_behavior(FlyBehavior)の飛行を実行します。
    # 飛ぶ振る舞いを実行するには、Duckはfly_behaviorが参照するオブジェクトに飛ぶように依頼するだけでよい
    def perform_fly(self):
        self.fly_behavior.fly()


# [Contextコンポーネントの一部]
# カモサブクラス(マガモ)
class MallardDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("マガモを表示するよ!")

# カモサブクラス(赤毛ガモ)
class RedheadDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("赤毛ガモを表示するよ!")


# マガモを使う例
mallard_duck_model = MallardDuck()
mallard_duck_model.display()
mallard_duck_model.quack()
mallard_duck_model.perform_fly()

# 赤毛ガモを使う例
red_head_duck_model = RedheadDuck()
red_head_duck_model.display()
red_head_duck_model.quack()
red_head_duck_model.perform_fly()

これに先ほどと同様以下の要件の変更が入ったとします。

要件の追加

新たにゴム製のカモが必要になりました。
この際、ゴム製カモは以下の挙動が必要になります。
・ゴム製カモはジェット機構をつかって飛びます。

ゴム製カモのためにコードを修正

要件の追加に従ってコードを修正してみましょう。
以下の手順でコードの修正を行います。

  • ジェット機構で飛ぶ振る舞いを追加(FlyWithJetクラス)
  • ゴム製カモのサブクラスを追加
from abc import ABC, abstractmethod

# [Strategyコンポーネント]
# 飛ぶ振る舞いをするためのインターフェース
class FlyBehavior(ABC):
    # (省略)

# [ConcreteStrategyコンポーネント]
# 飛ぶ振る舞いの具体的な実装(羽ばたく場合)
class FlyWithWings(FlyBehavior):
    # (省略)

# 飛ぶ振る舞いの具体的な実装(ジェット機構の場合)
class FlyWithJet(FlyBehavior):
    def fly(self):
        print("ジェット機構で飛ぶよ!")

# [Contextコンポーネント]
# スーパークラスの定義
class Duck(ABC):
    # (省略)

# [Contextコンポーネントの一部]
# カモサブクラス(ゴム製カモ)
class RubberDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithJet()

    def display(self):
        print("ゴム製ガモを表示するよ!")

    # (省略)


# マガモを使う
mallard_duck_model = MallardDuck()
mallard_duck_model.perform_fly()  # 羽ばたいて飛ぶよ!

# 赤毛ガモを使う
red_head_duck_model = RedheadDuck()
red_head_duck_model.perform_fly()  # 羽ばたいて飛ぶよ!

# ゴム製カモを使う
rubber_duck_model = RubberDuck()
rubber_duck_model.perform_fly()  # ジェット機構で飛ぶよ!

ここではFlyWithJetクラスを新しく追加し、ゴム製カモのサブクラスを定義しています。
アンチパターンのときみたいに既存のDuckクラスに手を入れるようなことはしていませんね。

良い部分
特定の振る舞い(この場合は飛ぶ振る舞い)に焦点を当てて、それをカプセル化し、アルゴリズムを定義しています。
既存のコードは変更していないため、マガモや赤毛ガモの動作には影響しない可能となっています。

また、今回は泳ぐ振る舞いや鳴く振る舞いは固定のものとして扱っていますが、
必要に応じてこれらもStrategyパターンに沿ったコードに変更しましょう。

何でもかんでもカプセル化すると逆に可読性が下がる可能性もあります。
システムの要件や将来の拡張性を考慮して、振る舞いを固定のままにしておくかカプセル化するかを判断しましょう。

振る舞いを動的に設定できるようにする

ここまでで十分Strategyデザインパターンに沿ったコードとなりました。

最後に飛ぶ振る舞いを動的に設定できるようにしましょう。
こうすることで、最初は「羽ばたいて」飛んでいたカモも、「ジェット機構」で飛べるよう進化することができます!!

以下の手順でコードの修正を行います。

  • 飛ぶ振る舞いを変更するセッターメソッドを追加(set_fly_behavior)
from abc import ABC, abstractmethod

# [Strategyコンポーネント]
# 飛ぶ振る舞いをするためのインターフェース
class FlyBehavior(ABC):
    # (省略)

# [ConcreteStrategyコンポーネント]
# 飛ぶ振る舞いの具体的な実装(羽ばたく場合)
class FlyWithWings(FlyBehavior):
    # (省略)

# 飛ぶ振る舞いの具体的な実装(ジェット機構の場合)
class FlyWithJet(FlyBehavior):
    # (省略)

# [Contextコンポーネント]
# スーパークラスの定義
class Duck(ABC):
    # 飛ぶ振る舞いを動的に設定するメソッドです。
    def set_fly_behavior(self, fb: FlyBehavior):
        self.fly_behavior = fb

    # (省略)

# [Contextコンポーネントの一部]
# カモサブクラス(ゴム製カモ)
class RubberDuck(Duck):
    # (省略)


# マガモを使う
mallard_duck_model = MallardDuck()
mallard_duck_model.perform_fly()  # 羽ばたいて飛ぶよ!
# 飛ぶ振る舞いを変更する
mallard_duck_model.set_fly_behavior(FlyWithJet())
mallard_duck_model.perform_fly()  # ジェット機構で飛ぶよ!

良い部分
Duckインスタンスの飛ぶ振る舞いを柔軟に変更することができます。
例えば、新しい飛ぶ振る舞いが追加された場合(例:ジェットエンジンで飛ぶ)、それをFlyBehaviorとして実装し、set_fly_behaviorを使って既存のDuckオブジェクトに適用することができます。

まとめ

このようにStrategyパターンに沿ったことによって変更に強いコードとなります。
とりあえず動作させるための小手先の修正としてアンチパターンでの実装になりがちですが、
長い目で見ればデザインパターンを取り入れたコードのほうが得られる恩恵は大きそうですね。(戒め)

参考

今回のコードの完成形

from abc import ABC, abstractmethod

# [Strategyコンポーネント]
# 飛ぶ振る舞いをするためのインターフェース
class FlyBehavior(ABC):
    @abstractmethod
    def fly(self):
        pass

# [ConcreteStrategyコンポーネント]
# 飛ぶ振る舞いの具体的な実装(羽ばたく場合)
class FlyWithWings(FlyBehavior):
    def fly(self):
        print("羽ばたいて飛ぶよ!")

# 飛ぶ振る舞いの具体的な実装(ジェット機構の場合)
class FlyWithJet(FlyBehavior):
    def fly(self):
        print("ジェット機構で飛ぶよ!")


# [Contextコンポーネント]
# スーパークラスの定義
class Duck(ABC):
    # fly_behaviorとquack_behaviorはFlyBehaviorクラスとQuackBehaviorクラスのインスタンスであることを示します。
    # すべてのカモサブクラスはこの2つの変数を継承します。
    fly_behavior: FlyBehavior = None

    # カモが泳ぐ振る舞いの実装
    def swim(self):
        print("足をバタつかせて泳ぐよ!")

    # カモが鳴く振る舞いの実装
    def quack(self):
        print("ガーガーと鳴くよ!")

    @abstractmethod
    def display(self):
        # 抽象メソッド
        raise NotImplementedError

    # perform_fly()メソッドで、Duckクラスに指定されたfly_behavior(FlyBehavior)の飛行を実行します。
    # 飛ぶ振る舞いを実行するには、Duckはfly_behaviorが参照するオブジェクトに飛ぶように依頼するだけでよい
    def perform_fly(self):
        self.fly_behavior.fly()

    # 飛ぶ振る舞いを動的に設定するメソッドです。
    def set_fly_behavior(self, fb: FlyBehavior):
        self.fly_behavior = fb


# [Contextコンポーネントの一部]
# カモサブクラス(マガモ)
class MallardDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("マガモを表示するよ!")


# カモサブクラス(赤毛ガモ)
class RedheadDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("赤毛ガモを表示するよ!")


# カモサブクラス(ゴム製カモ)
class RubberDuck(Duck):
    def __init__(self):
        self.fly_behavior = FlyWithJet()

    def display(self):
        print("ゴム製ガモを表示するよ!")


# マガモを使う例
mallard_duck_model = MallardDuck()
mallard_duck_model.display()
mallard_duck_model.quack()
mallard_duck_model.perform_fly()  # 羽ばたいて飛ぶよ!
# 飛ぶ振る舞いを変更する
mallard_duck_model.set_fly_behavior(FlyWithJet())
mallard_duck_model.perform_fly()  # ジェット機構で飛ぶよ!

# 赤毛ガモを使う例
red_head_duck_model = RedheadDuck()
red_head_duck_model.display()
red_head_duck_model.quack()
red_head_duck_model.perform_fly()  # 羽ばたいて飛ぶよ!

# ゴム製カモを使う
rubber_duck_model = RubberDuck()
rubber_duck_model.perform_fly()  # ジェット機構で飛ぶよ!
18
31
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
18
31