PythonによるCommandパターンとFactory Methodの実践的活用法

## はじめに

Pythonでアプリケーションを開発していると、似たような処理フローだけど少しずつ異なる実装が必要なケースに遭遇することがあります。
例えば：

- ユーザー種別によって異なる計算ロジック
- 支払い方法ごとに異なる決済処理
- データ形式に応じた異なる変換処理

このような場合、単純に条件分岐を書いていくと、コードが複雑化し保守性が低下していきます。
この記事では、CommandパターンとFactory Methodパターンを組み合わせることで、
どのように柔軟で保守性の高い実装を実現できるかをPythonのコードで解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/d72e80f0-62a7-a03d-bd5e-8175ce344ba3.png)

## このパターンを使うべき状況

以下のような状況で特に有効です：

1. 似たような処理フローが複数存在する
2. 新しい処理バリエーションが追加される可能性が高い
3. 処理の実行者が具体的な実装を知る必要がない
4. 処理のパラメータ化や履歴管理が必要

## 実装例

具体例として、ECサイトでの商品注文処理を実装してみましょう。
支払い方法によって処理が異なる状況を想定します。

```mermaid
classDiagram
    class OrderCommand {
        <<interface>>
        +execute()
        +get_result()
    }
    
    class OrderResult {
        <<dataclass>>
        +success: bool
        +message: str
        +transaction_id: Optional[str]
    }
    
    class OrderDetails {
        <<dataclass>>
        +amount: int
        +customer_id: str
    }
    
    class PaymentMethod {
        <<enum>>
        CREDIT_CARD
        BANK_TRANSFER
        CONVENIENCE_STORE
    }
    
    class CreditCardOrderCommand {
        -_result: OrderResult
        +execute()
        +get_result()
    }
    
    class BankTransferOrderCommand {
        -_result: OrderResult
        +execute()
        +get_result()
    }
    
    class ConvenienceStoreOrderCommand {
        -_result: OrderResult
        +execute()
        +get_result()
    }
    
    class OrderCommandFactory {
        +create_command(payment_method: PaymentMethod, order_details: OrderDetails): OrderCommand
    }

    OrderCommand <|.. CreditCardOrderCommand
    OrderCommand <|.. BankTransferOrderCommand
    OrderCommand <|.. ConvenienceStoreOrderCommand
    
    OrderCommand *-- OrderResult
    OrderCommand *-- OrderDetails
    
    OrderCommandFactory ..> OrderCommand : creates
    OrderCommandFactory ..> PaymentMethod : uses
    OrderCommandFactory ..> OrderDetails : uses

    note for OrderCommand "Abstract Base Class\ndefines interface for\nall payment commands"
    note for OrderCommandFactory "Creates appropriate command\nbased on payment method"
```

```mermaid
sequenceDiagram
    autonumber
    
    participant Client as Client
    participant Factory as OrderCommandFactory
    participant Command as OrderCommand
    participant Service as PaymentService
    
    Note over Client,Service: 正常系処理フロー
    Client->>+Factory: create_command(PaymentMethod, OrderDetails)
    Factory-->>-Client: command instance
    
    Client->>+Command: execute()
    Command->>+Service: process_payment()
    Service-->>-Command: payment_result
    Command-->>-Client: OrderResult(success=True)
    
    Note over Client,Service: エラー処理フロー
    Client->>+Factory: create_command(PaymentMethod, OrderDetails)
    Factory-->>-Client: command instance
    
    Client->>+Command: execute()
    Command->>+Service: process_payment()
    Service-->>-Command: error response
    Command-->>-Client: OrderResult(success=False)
```

```python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum, auto
from typing import Optional

# 支払い方法の列挙型
class PaymentMethod(Enum):
    CREDIT_CARD = auto()
    BANK_TRANSFER = auto()

# 注文詳細のデータクラス
@dataclass
class OrderDetails:
    amount: int
    customer_id: str
    
# 実行結果のデータクラス
@dataclass
class OrderResult:
    success: bool
    message: str
    transaction_id: Optional[str] = None

# 基本となるコマンドの抽象クラス
class OrderCommand(ABC):
    def __init__(self, order_details: OrderDetails):
        self.order_details = order_details
        self._result: Optional[OrderResult] = None
    
    @abstractmethod
    def execute(self) -> None:
        pass
    
    @property
    def result(self) -> Optional[OrderResult]:
        return self._result

# クレジットカード決済用のコマンド
class CreditCardOrderCommand(OrderCommand):
    def execute(self) -> None:
        # クレジットカード決済の実装
        print(f"クレジットカードで決済を実行: ¥{self.order_details.amount}")
        # 実際の決済処理をここで実装
        self._result = OrderResult(
            success=True,
            message="クレジットカード決済が完了しました",
            transaction_id="CC-2024-001"
        )

# 銀行振込用のコマンド
class BankTransferOrderCommand(OrderCommand):
    def execute(self) -> None:
        # 銀行振込用の実装
        print(f"銀行振込の受付を実行: ¥{self.order_details.amount}")
        # 振込先情報の生成などの処理をここで実装
        self._result = OrderResult(
            success=True,
            message="銀行振込の受付が完了しました",
            transaction_id="BT-2024-001"
        )

# コマンドのファクトリークラス
class OrderCommandFactory:
    @staticmethod
    def create_command(payment_method: PaymentMethod, order_details: OrderDetails) -> OrderCommand:
        command_map = {
            PaymentMethod.CREDIT_CARD: CreditCardOrderCommand,
            PaymentMethod.BANK_TRANSFER: BankTransferOrderCommand,
        }
        
        command_class = command_map.get(payment_method)
        if command_class is None:
            raise ValueError(f"Unsupported payment method: {payment_method}")
        
        return command_class(order_details)

# 使用例
def process_order(payment_method: PaymentMethod, order_details: OrderDetails) -> OrderResult:
    try:
        # コマンドの生成
        command = OrderCommandFactory.create_command(payment_method, order_details)
        # コマンドの実行
        command.execute()
        
        if command.result is None:
            raise RuntimeError("Command execution did not produce a result")
        
        return command.result
    except Exception as e:
        return OrderResult(success=False, message=f"Error processing order: {str(e)}")

# メイン処理
def main():
    # 注文情報の作成
    order_details = OrderDetails(amount=10000, customer_id="CUST-001")
    
    # クレジットカード決済の実行
    print("\n=== クレジットカード決済のテスト ===")
    credit_result = process_order(PaymentMethod.CREDIT_CARD, order_details)
    print(f"結果: {credit_result}")
    
    # 銀行振込の実行
    print("\n=== 銀行振込決済のテスト ===")
    bank_result = process_order(PaymentMethod.BANK_TRANSFER, order_details)
    print(f"結果: {bank_result}")

if __name__ == "__main__":
    main()
```

## パターンのメリット

1. **拡張性が高い**
   - Pythonの動的性を活かしつつ、型ヒントで安全性も確保
   - 新しい支払い方法の追加が容易（Open-Closed Principleの遵守）
   - ファクトリーのマッピング辞書に追加するだけで対応可能

2. **責務の分離が明確**
   - 各Commandクラスが特定の処理に特化
   - データクラスやEnum使用による明確なインターフェース
   - 型ヒントによる意図の明確化

3. **テストが容易**
   - 各Commandクラスを個別にテスト可能
   - モックやスタブの作成が容易
   ```python
   # テスト例
   def test_credit_card_command():
       order_details = OrderDetails(amount=1000, customer_id="TEST-001")
       command = CreditCardOrderCommand(order_details)
       command.execute()
       assert command.result is not None
       assert command.result.success
       assert command.result.transaction_id is not None
   ```

4. **エラーハンドリングが統一的**
   - 例外処理が集中化され、管理が容易
   - 型ヒントによるバグの早期発見

## 実装の拡張例

たとえば、コンビニ決済を追加する場合：

```python
# 支払い方法の列挙型に追加
class PaymentMethod(Enum):
    CREDIT_CARD = auto()
    BANK_TRANSFER = auto()
    CONVENIENCE_STORE = auto()  # 追加

# コンビニ決済用のコマンドを実装
class ConvenienceStoreOrderCommand(OrderCommand):
    def execute(self) -> None:
        print(f"コンビニ決済の受付を実行: ¥{self.order_details.amount}")
        # 支払い番号の生成など
        payment_number = "12345678"  # 実際は適切な番号生成ロジックを実装
        self._result = OrderResult(
            success=True,
            message=f"コンビニ決済の受付が完了しました。支払い番号: {payment_number}",
            transaction_id=f"CV-2024-{payment_number}"
        )

# ファクトリーのマッピングに追加
class OrderCommandFactory:
    @staticmethod
    def create_command(payment_method: PaymentMethod, order_details: OrderDetails) -> OrderCommand:
        command_map = {
            PaymentMethod.CREDIT_CARD: CreditCardOrderCommand,
            PaymentMethod.BANK_TRANSFER: BankTransferOrderCommand,
            PaymentMethod.CONVENIENCE_STORE: ConvenienceStoreOrderCommand,  # 追加
        }
        
        command_class = command_map.get(payment_method)
        if command_class is None:
            raise ValueError(f"Unsupported payment method: {payment_method}")
        
        return command_class(order_details)
```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4ac39032-f047-88a5-1ccf-7a830601f13a.png)


PythonでのCommandパターンとFactory Methodパターンの組み合わせは、
特に以下の点で効果的です：

1. コードの見通しの良さ
2. 拡張性の高さ
3. テストのしやすさ
4. エラーハンドリングの統一性

これらのパターンは、アプリケーションの規模や要件に応じて適切に採用することが重要です。
小規模なプロジェクトでは、かえって複雑になる可能性があるため、
状況に応じて判断することをお勧めします。