2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

StrategyパターンとFactory Methodパターン:総合的な比較と実践的活用

Posted at

はじめに

ソフトウェア設計において、StrategyパターンとFactory Methodパターンは頻繁に使用される重要なデザインパターンです。本記事では、これらのパターンの特徴、使用例、比較、そして関連性について総合的に解説します。

image.png

目次

  1. なぜStrategyとFactory Methodを比較するのか
  2. Strategyパターン
  3. Factory Methodパターン
  4. パターンの比較
  5. パターンの組み合わせ
  6. 他のデザインパターンとの関連
  7. まとめ

なぜStrategyとFactory Methodを比較するのか

  1. 使用頻度: 両パターンとも実務でよく使用され、多くの開発者が日常的に遭遇します。

  2. 目的の類似性: 両パターンとも、オブジェクト指向設計の柔軟性を高めることを目的としています。

  3. 適用範囲の違い: Strategyは振る舞いの変更、Factory Methodはオブジェクト生成に焦点を当てており、適用範囲が異なります。

  4. 組み合わせの可能性: これらのパターンは、しばしば組み合わせて使用されることがあります。

  5. 設計の選択: パターンの特徴を理解し比較することで、適切な設計選択ができるようになります。

Strategyパターン

Strategyパターンは、アルゴリズムの族を定義し、それぞれをカプセル化して交換可能にするパターンです。

クラス図

実装例:支払い方法

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> None:
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"クレジットカードで{amount}円支払いました。")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"PayPalで{amount}円支払いました。")

class Payment:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def process_payment(self, amount: float) -> None:
        self.strategy.pay(amount)

    def set_strategy(self, strategy: PaymentStrategy) -> None:
        self.strategy = strategy

# 使用例
if __name__ == "__main__":
    payment = Payment(CreditCardPayment())
    payment.process_payment(1000)
    
    payment.set_strategy(PayPalPayment())
    payment.process_payment(2000)

実行結果

クレジットカードで1000円支払いました。
PayPalで2000円支払いました。

解説

  • PaymentStrategyは抽象基底クラスで、具体的な支払い方法はこれを継承して実装します。
  • Paymentクラス(コンテキスト)はPaymentStrategyを保持し、必要に応じて切り替えることができます。
  • このパターンにより、新しい支払い方法の追加が容易になり、既存のコードを変更せずに支払いロジックを拡張できます。

Factory Methodパターン

Factory Methodパターンは、オブジェクトの作成をサブクラスに委ねるパターンです。

クラス図

実装例:ログ出力方法

from abc import ABC, abstractmethod

class Logger(ABC):
    @abstractmethod
    def log(self, message: str) -> None:
        pass

class ConsoleLogger(Logger):
    def log(self, message: str) -> None:
        print(f"Console: {message}")

class FileLogger(Logger):
    def log(self, message: str) -> None:
        print(f"File: {message}")  # 実際にはファイルに書き込む

class LoggerFactory(ABC):
    @abstractmethod
    def create_logger(self) -> Logger:
        pass

    def get_logger(self) -> Logger:
        logger = self.create_logger()
        return logger

class ConsoleLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return ConsoleLogger()

class FileLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return FileLogger()

# 使用例
def client_code(factory: LoggerFactory) -> None:
    logger = factory.get_logger()
    logger.log("This is a test message")

if __name__ == "__main__":
    print("Using Console Logger:")
    client_code(ConsoleLoggerFactory())
    
    print("\nUsing File Logger:")
    client_code(FileLoggerFactory())

実行結果

Using Console Logger:
Console: This is a test message

Using File Logger:
File: This is a test message

解説

  • LoggerFactoryは抽象基底クラスで、具体的なファクトリクラスはこれを継承して実装します。
  • create_loggerメソッドがFactory Methodで、具体的なLoggerオブジェクトの生成を行います。
  • このパターンにより、ログ出力方法の追加が容易になり、クライアントコードを変更せずにロギングシステムを拡張できます。

パターンの比較

特徴 Strategyパターン Factory Methodパターン
主な用途 アルゴリズムの切り替え オブジェクト生成の抽象化
焦点 振る舞い(メソッド) オブジェクト生成
実行時の変更 容易 通常は固定
クラス構造 比較的フラット 階層的
新しい実装の追加 新しいStrategyクラスを追加 新しいCreatorとProductクラスを追加
主な利点 アルゴリズムの動的切り替え オブジェクト生成ロジックの分離
適用シーン 複数のアルゴリズムが存在し、実行時に切り替えたい場合 クラスのインスタンス化を抽象化し、サブクラスで決定したい場合

パターンの組み合わせ

StrategyパターンとFactory Methodパターンは、以下のように組み合わせて使用することができます。

from abc import ABC, abstractmethod

# Strategy
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> None:
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"クレジットカードで{amount}円支払いました。")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"PayPalで{amount}円支払いました。")

# Simple Factory
class PaymentStrategyFactory:
    @staticmethod
    def create_payment_strategy(payment_type: str) -> PaymentStrategy:
        if payment_type == "credit_card":
            return CreditCardPayment()
        elif payment_type == "paypal":
            return PayPalPayment()
        else:
            raise ValueError(f"Unknown payment type: {payment_type}")

# Context
class Payment:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def process_payment(self, amount: float) -> None:
        self.strategy.pay(amount)

    def set_strategy(self, strategy: PaymentStrategy) -> None:
        self.strategy = strategy

# Client code
if __name__ == "__main__":
    payment_types = ["credit_card", "paypal"]
    for payment_type in payment_types:
        strategy = PaymentStrategyFactory.create_payment_strategy(payment_type)
        payment = Payment(strategy)
        payment.process_payment(1000)

実行結果

クレジットカードで1000円支払いました。
PayPalで1000円支払いました。

この例では、Simple Factoryパターン(Factory Methodの簡略版)を使用して適切なStrategyを生成し、Strategyパターンで使用しています。これにより、支払い方法の選択と実行を柔軟に行うことができます。

他のデザインパターンとの関連

  1. Abstract Factoryパターン: Factory Methodパターンの拡張で、関連するオブジェクトのファミリーを作成する際に使用されます。

  2. Commandパターン: Strategyパターンと似ていますが、コマンドのキューイングや履歴管理などの追加機能を提供します。

  3. Decoratorパターン: 既存のオブジェクトに新しい責任を動的に追加する際に使用され、Strategyパターンと組み合わせて使用されることがあります。

  4. Dependency Injection: オブジェクトの依存関係を外部から注入するテクニックで、StrategyパターンやFactory Methodパターンと相性が良いです。

まとめ

  • Strategyパターンは、アルゴリズムの動的な切り替えに適しており、振る舞いの変更を容易にします。
  • Factory Methodパターンは、オブジェクト生成の抽象化に適しており、オブジェクト生成の柔軟性を高めます。
  • これらのパターンを比較することで、それぞれの特徴と適用場面をより深く理解できます。
  • パターンを組み合わせることで、より柔軟で拡張性の高いシステムを構築できます。
  • デザインパターンの選択は、問題の性質や設計の目標に応じて行うべきです。

StrategyパターンとFactory Methodパターンの特徴、関連性、そして他のパターンとの関係を理解することで、より適切なパターンの選択と応用が可能になります。実際の開発では、これらのパターンを単独で使用するだけでなく、状況に応じて組み合わせたり他のパターンと併用したりすることで、より強力で柔軟な設計ソリューションを実現できます。

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?