はじめに
ソフトウェア設計において、StrategyパターンとFactory Methodパターンは頻繁に使用される重要なデザインパターンです。本記事では、これらのパターンの特徴、使用例、比較、そして関連性について総合的に解説します。
目次
- なぜStrategyとFactory Methodを比較するのか
- Strategyパターン
- Factory Methodパターン
- パターンの比較
- パターンの組み合わせ
- 他のデザインパターンとの関連
- まとめ
なぜStrategyとFactory Methodを比較するのか
-
使用頻度: 両パターンとも実務でよく使用され、多くの開発者が日常的に遭遇します。
-
目的の類似性: 両パターンとも、オブジェクト指向設計の柔軟性を高めることを目的としています。
-
適用範囲の違い: Strategyは振る舞いの変更、Factory Methodはオブジェクト生成に焦点を当てており、適用範囲が異なります。
-
組み合わせの可能性: これらのパターンは、しばしば組み合わせて使用されることがあります。
-
設計の選択: パターンの特徴を理解し比較することで、適切な設計選択ができるようになります。
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パターンで使用しています。これにより、支払い方法の選択と実行を柔軟に行うことができます。
他のデザインパターンとの関連
-
Abstract Factoryパターン: Factory Methodパターンの拡張で、関連するオブジェクトのファミリーを作成する際に使用されます。
-
Commandパターン: Strategyパターンと似ていますが、コマンドのキューイングや履歴管理などの追加機能を提供します。
-
Decoratorパターン: 既存のオブジェクトに新しい責任を動的に追加する際に使用され、Strategyパターンと組み合わせて使用されることがあります。
-
Dependency Injection: オブジェクトの依存関係を外部から注入するテクニックで、StrategyパターンやFactory Methodパターンと相性が良いです。
まとめ
- Strategyパターンは、アルゴリズムの動的な切り替えに適しており、振る舞いの変更を容易にします。
- Factory Methodパターンは、オブジェクト生成の抽象化に適しており、オブジェクト生成の柔軟性を高めます。
- これらのパターンを比較することで、それぞれの特徴と適用場面をより深く理解できます。
- パターンを組み合わせることで、より柔軟で拡張性の高いシステムを構築できます。
- デザインパターンの選択は、問題の性質や設計の目標に応じて行うべきです。
StrategyパターンとFactory Methodパターンの特徴、関連性、そして他のパターンとの関係を理解することで、より適切なパターンの選択と応用が可能になります。実際の開発では、これらのパターンを単独で使用するだけでなく、状況に応じて組み合わせたり他のパターンと併用したりすることで、より強力で柔軟な設計ソリューションを実現できます。