DIパターン × Factoryパターン 〜柔軟なオブジェクト生成の実装

## はじめに
DIパターンとFactoryパターンの組み合わせは、柔軟でテスト可能なアプリケーションを構築する上で重要なデザインパターンです。この記事では、Pythonでの実装例を通じて、両パターンの効果的な使い方を解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e77479fc-1a9d-4fda-4516-376b8d61d8bf.png)


## なぜDIパターンとFactoryパターンを組み合わせるのか？

### 解決したい課題
1. オブジェクトの生成と使用の分離
2. テスト容易性の向上
3. 依存関係の明確化
4. 実装の切り替えを容易にする

### メリット
- 依存関係の制御が容易になる
- テストの書きやすさが向上する
- コードの再利用性が高まる
- 実装の差し替えが容易になる

### デメリット
- 初期学習コストが高い
- 小規模なプロジェクトでは過剰な可能性がある
- ボイラープレートコードが増える

## 実装例

以下に、ログ出力システムを例にした実装を示します。

```mermaid
classDiagram
    class Logger {
        <<interface>>
        +log(message: str)
    }
    
    class LoggerFactory {
        <<interface>>
        +create_logger() Logger
    }
    
    class ConsoleLogger {
        +log(message: str)
    }
    
    class FileLogger {
        +log(message: str)
    }
    
    class ConsoleLoggerFactory {
        +create_logger() Logger
    }
    
    class FileLoggerFactory {
        +create_logger() Logger
    }
    
    class UserService {
        -logger: Logger
        +__init__(logger_factory: LoggerFactory)
        +create_user(username: str)
    }
    
    Logger <|.. ConsoleLogger
    Logger <|.. FileLogger
    LoggerFactory <|.. ConsoleLoggerFactory
    LoggerFactory <|.. FileLoggerFactory
    ConsoleLoggerFactory ..> ConsoleLogger : creates
    FileLoggerFactory ..> FileLogger : creates
    UserService --> Logger : uses
    UserService --> LoggerFactory : uses
```

```mermaid
sequenceDiagram
    actor Client
    participant US as UserService
    participant FLF as FileLoggerFactory
    participant CLF as ConsoleLoggerFactory
    participant FL as FileLogger
    participant CL as ConsoleLogger

    Client->>CLF: create ConsoleLoggerFactory()
    activate CLF
    CLF-->>Client: console_factory
    deactivate CLF

    Client->>FLF: create FileLoggerFactory()
    activate FLF
    FLF-->>Client: file_factory
    deactivate FLF

    Client->>US: new UserService(console_factory)
    activate US
    US->>CLF: create_logger()
    activate CLF
    CLF-->>US: console_logger
    deactivate CLF
    US-->>Client: service
    deactivate US

    Client->>US: create_user("John")
    activate US
    US->>CL: log("Created user: John")
    activate CL
    CL-->>US: void
    deactivate CL
    US-->>Client: void
    deactivate US

    Client->>US: new UserService(file_factory)
    activate US
    US->>FLF: create_logger()
    activate FLF
    FLF-->>US: file_logger
    deactivate FLF
    US-->>Client: service
    deactivate US

    Client->>US: create_user("Alice")
    activate US
    US->>FL: log("Created user: Alice")
    activate FL
    FL-->>US: void
    deactivate FL
    US-->>Client: void
    deactivate US
```

```python
from abc import ABC, abstractmethod
from typing import List

# インターフェース定義
class Logger(ABC):
    @abstractmethod
    def log(self, message: str) -> None:
        pass

class LoggerFactory(ABC):
    @abstractmethod
    def create_logger(self) -> Logger:
        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}")
        # 実際のファイル出力処理は省略

# Factory実装
class ConsoleLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return ConsoleLogger()

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

# サービスクラス
class UserService:
    def __init__(self, logger_factory: LoggerFactory):
        self.logger = logger_factory.create_logger()

    def create_user(self, username: str) -> None:
        # ユーザー作成のロジック
        self.logger.log(f"Created user: {username}")

# 使用例
def main():
    # コンソール出力を使用する場合
    console_logger_factory = ConsoleLoggerFactory()
    user_service_with_console = UserService(console_logger_factory)
    user_service_with_console.create_user("John")

    # ファイル出力を使用する場合
    file_logger_factory = FileLoggerFactory()
    user_service_with_file = UserService(file_logger_factory)
    user_service_with_file.create_user("Alice")

def run_examples():
    print("\n=== 基本的な使用例 ===")
    main()

    print("\n=== 環境に応じたロガーの切り替え ===")
    dev_service = configure_service(Environment.DEVELOPMENT)
    dev_service.create_user("DevUser")
    
    staging_service = configure_service(Environment.STAGING)
    staging_service.create_user("StagingUser")

    print("\n=== 複数ロガーの組み合わせ ===")
    composite_factory = setup_composite_logging()
    composite_service = UserService(composite_factory)
    composite_service.create_user("CompositeUser")

if __name__ == "__main__":
    run_examples()

"""
実行結果:
=== 基本的な使用例 ===
[Console] Created user: John
[File] Created user: Alice

=== 環境に応じたロガーの切り替え ===
[Console] Created user: DevUser
[File] Created user: StagingUser

=== 複数ロガーの組み合わせ ===
[Console] Created user: CompositeUser
[File] Created user: CompositeUser
"""
```

## テストの例

```python
import unittest
from typing import List

class MockLogger(Logger):
    def __init__(self):
        self.logs: List[str] = []

    def log(self, message: str) -> None:
        self.logs.append(message)

class MockLoggerFactory(LoggerFactory):
    def __init__(self):
        self.mock_logger = MockLogger()

    def create_logger(self) -> Logger:
        return self.mock_logger

    def get_mock_logger(self) -> MockLogger:
        return self.mock_logger

class TestUserService(unittest.TestCase):
    def test_create_user(self):
        # テストのセットアップ
        mock_factory = MockLoggerFactory()
        user_service = UserService(mock_factory)
        mock_logger = mock_factory.get_mock_logger()

        # テスト実行
        user_service.create_user("TestUser")

        # 検証
        self.assertEqual(mock_logger.logs[0], "Created user: TestUser")

if __name__ == '__main__':
    unittest.main()
```

## 実践的な拡張例

### 1. 環境に応じたロガーの切り替え

```python
from enum import Enum

class Environment(Enum):
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"

class LoggerFactoryProvider:
    @staticmethod
    def get_factory(env: Environment) -> LoggerFactory:
        if env == Environment.DEVELOPMENT:
            return ConsoleLoggerFactory()
        elif env == Environment.STAGING:
            return FileLoggerFactory()
        else:
            return CloudLoggerFactory()  # 本番環境用のロガー

# 使用例
def configure_service(env: Environment) -> UserService:
    factory = LoggerFactoryProvider.get_factory(env)
    return UserService(factory)
```

### 2. 複数のロガーを組み合わせる

```python
class CompositeLogger(Logger):
    def __init__(self, loggers: List[Logger]):
        self.loggers = loggers

    def log(self, message: str) -> None:
        for logger in self.loggers:
            logger.log(message)

class CompositeLoggerFactory(LoggerFactory):
    def __init__(self, factories: List[LoggerFactory]):
        self.factories = factories

    def create_logger(self) -> Logger:
        loggers = [factory.create_logger() for factory in self.factories]
        return CompositeLogger(loggers)

# 使用例
def setup_composite_logging():
    factories = [
        ConsoleLoggerFactory(),
        FileLoggerFactory()
    ]
    return CompositeLoggerFactory(factories)
```

## 実践的なユースケース

1. データベース接続の切り替え
   - 開発環境とテスト環境で異なるDBを使用
   - モックDBでのテスト実行

2. 外部APIクライアントの差し替え
   - 本番環境と開発環境での接続先切り替え
   - テスト時のモック化

3. キャッシュ実装の切り替え
   - メモリキャッシュとRedisの切り替え
   - テスト時のモックキャッシュ使用

4. 設定の動的ロード
   - 環境変数からの設定読み込み
   - 設定ファイルからの読み込み

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ef745130-3770-1d5d-281a-c099ee21f558.png)

DIパターンとFactoryパターンの組み合わせは、以下のような場面で特に有効です：

- テスタビリティを重視する場合
- 実行時に実装を切り替えたい場合
- 依存関係を明確に管理したい場合
- マイクロサービスアーキテクチャでの実装

この組み合わせにより、保守性が高く、テストが容易なコードベースを実現できます。Pythonの型ヒントと抽象基底クラスを活用することで、より堅牢な実装が可能になります。