Mediator パターンで複雑な相互作用をシンプルに：データ変換システムの設計

 ## はじめに

ソフトウェア開発において、複数のコンポーネント間の相互作用を管理することは常に課題となります。特に、データ変換のような複雑な処理では、さまざまなフォーマット間の変換ロジックが絡み合い、コードの管理が困難になりがちです。このような状況で、Mediatorパターンは非常に有効な解決策となります。

本記事では、Mediatorパターンを用いてデータ変換システムを設計・実装する方法を紹介します。このパターンを使用する理由、適切な使用場面、そしてメリットとデメリットについて詳しく説明します。

## Mediatorパターンとは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/95062690-9ff9-77cf-4bdb-937410ac76aa.png)

Mediatorパターンは、オブジェクト指向プログラミングにおけるデザインパターンの一つで、オブジェクト間の複雑な相互作用をカプセル化し、集中管理するためのパターンです。

### なぜMediatorパターンを使うのか

1. **結合度の低減**: 直接的な相互作用を減らし、コンポーネント間の依存関係を緩和します。
2. **再利用性の向上**: 個々のコンポーネントが独立性を保ち、他のシステムでも再利用しやすくなります。
3. **拡張性の確保**: 新しいコンポーネントの追加が容易になり、システムの拡張が簡単になります。
4. **集中管理**: 相互作用のロジックを一箇所に集中させることで、管理と変更が容易になります。

### 使い所

Mediatorパターンは以下のような状況で特に有効です：

- 多数のコンポーネントが複雑に相互作用する場合
- コンポーネント間の依存関係が複雑で管理が困難な場合
- システムの拡張性や柔軟性を高めたい場合
- コードの再利用性を向上させたい場合

## メリット

1. **コードの整理**: 相互作用のロジックが一箇所に集中するため、コードが整理されます。
2. **保守性の向上**: 変更が必要な場合、Mediatorクラスのみを修正すれば良いため、保守が容易になります。
3. **柔軟性**: 新しい相互作用を追加する際、既存のコンポーネントに影響を与えずに実装できます。
4. **テスト容易性**: コンポーネントを個別にテストしやすくなります。

## デメリット

1. **Mediatorの複雑化**: システムが大規模になると、Mediatorクラス自体が複雑になる可能性があります。
2. **オーバーヘッド**: 小規模なシステムでは、Mediatorパターンの導入がオーバーエンジニアリングになる可能性があります。
3. **パフォーマンス**: すべての相互作用がMediatorを経由するため、わずかなパフォーマンス低下が生じる可能性があります。

## 実装例：データ変換システム

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/35677422-a35a-80d6-2c91-3f615422b447.png)

Mediatorパターンを用いたデータ変換システムの実装例を見てみましょう。この例では、JSON、XML、CSVの間でデータを変換するシステムを構築します。

```plantuml
@startuml
skinparam classAttributeIconSize 0

title Mediatorパターンを用いたデータ変換システム

note as N1
Mediatorパターンの特徴:
1. Mediatorが中心となり、コンポーネント間の通信を管理
2. コンポーネント（Converters）は互いに直接通信せず、Mediatorを介して相互作用
3. 新しいコンポーネントの追加が容易（低結合）
4. システムの拡張性と保守性が向上
end note

abstract class DataConverter {
  +convert(data: Any): Any
}

class JSONtoXMLConverter {
  +convert(data: Dict): str
}

class XMLtoJSONConverter {
  +convert(data: str): Dict
}

class CSVtoJSONConverter {
  +convert(data: str): Dict
}

class DataConversionMediator {
  -converters: Dict[str, DataConverter]
  +add_converter(name: str, converter: DataConverter)
  +convert(source_format: str, target_format: str, data: Any): Any
}

DataConverter <|-- JSONtoXMLConverter
DataConverter <|-- XMLtoJSONConverter
DataConverter <|-- CSVtoJSONConverter
DataConversionMediator o-- DataConverter

note right of DataConversionMediator
  Mediator: 全てのConverterを管理し、
  変換処理を制御
end note

note left of DataConverter
  Converters: 特定のデータ変換を担当
  互いに直接通信しない
end note

note bottom of CSVtoJSONConverter
  新しいConverterの追加が容易:
  DataConverterを継承し
  Mediatorに登録するだけ
end note

@enduml
```

```plantuml
@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam maxMessageSize 60

title データ変換システムのシーケンス図

actor Client
participant "DataConversionMediator" as Mediator
participant "JSONtoXMLConverter" as JSON2XML
participant "XMLtoJSONConverter" as XML2JSON
participant "CSVtoJSONConverter" as CSV2JSON

note over Mediator, CSV2JSON
  Mediatorパターンの特徴:
  1. クライアントは直接Mediatorとやり取り
  2. Converterは互いに直接通信しない
  3. Mediatorが適切なConverterを選択・実行
end note

Client -> Mediator : convert(JSON, XML, data)
activate Mediator

Mediator -> JSON2XML : convert(data)
activate JSON2XML
JSON2XML --> Mediator : XML data
deactivate JSON2XML

Mediator --> Client : XML result

Client -> Mediator : convert(CSV, JSON, data)
activate Mediator

Mediator -> CSV2JSON : convert(data)
activate CSV2JSON
CSV2JSON --> Mediator : JSON data
deactivate CSV2JSON

Mediator --> Client : JSON result

Client -> Mediator : convert(CSV, XML, data)
activate Mediator

Mediator -> CSV2JSON : convert(data)
activate CSV2JSON
CSV2JSON --> Mediator : JSON data
deactivate CSV2JSON

Mediator -> JSON2XML : convert(JSON data)
activate JSON2XML
JSON2XML --> Mediator : XML data
deactivate JSON2XML

Mediator --> Client : XML result
deactivate Mediator

@enduml
```

```python
import abc
from typing import Dict, Any
import json
import xml.etree.ElementTree as ET
import csv
from io import StringIO

class DataConverter(abc.ABC):
    @abc.abstractmethod
    def convert(self, data: Any) -> Any:
        pass

class JSONtoXMLConverter(DataConverter):
    def convert(self, data: Dict) -> str:
        root = ET.Element("root")
        for key, value in data.items():
            elem = ET.SubElement(root, key)
            elem.text = str(value)
        return ET.tostring(root, encoding="unicode")

class XMLtoJSONConverter(DataConverter):
    def convert(self, data: str) -> Dict:
        root = ET.fromstring(data)
        return {elem.tag: elem.text for elem in root}

class CSVtoJSONConverter(DataConverter):
    def convert(self, data: str) -> Dict:
        reader = csv.DictReader(StringIO(data))
        return next(reader)

class DataConversionMediator:
    def __init__(self):
        self.converters: Dict[str, DataConverter] = {}

    def add_converter(self, name: str, converter: DataConverter):
        self.converters[name] = converter

    def convert(self, source_format: str, target_format: str, data: Any, intermediate_format: str = "JSON") -> Any:
        if source_format == target_format:
            return data

        conversion_key = f"{source_format}_to_{target_format}"
        if conversion_key in self.converters:
            return self.converters[conversion_key].convert(data)
        
        # 直接の変換がない場合、中間形式を経由して変換
        if f"{source_format}_to_{intermediate_format}" in self.converters and f"{intermediate_format}_to_{target_format}" in self.converters:
            intermediate_data = self.converters[f"{source_format}_to_{intermediate_format}"].convert(data)
            return self.converters[f"{intermediate_format}_to_{target_format}"].convert(intermediate_data)
        
        raise ValueError(f"Conversion from {source_format} to {target_format} is not supported")

# 使用例
if __name__ == "__main__":
    mediator = DataConversionMediator()
    
    mediator.add_converter("JSON_to_XML", JSONtoXMLConverter())
    mediator.add_converter("XML_to_JSON", XMLtoJSONConverter())
    mediator.add_converter("CSV_to_JSON", CSVtoJSONConverter())

    try:
        # JSONからXMLへの変換
        json_data = {"name": "John Doe", "age": "30", "city": "New York"}
        xml_result = mediator.convert("JSON", "XML", json_data)
        print("JSON to XML:")
        print(xml_result)
        print()

        # XMLからJSONへの変換
        xml_data = """
        <root>
        <name>Jane Doe</name>
        <age>28</age>
        <city>San Francisco</city>
        </root>
        """.strip()
        json_result = mediator.convert("XML", "JSON", xml_data)
        print("XML to JSON:")
        print(json_result)
        print()

        # CSVからXMLへの変換（中間形式としてJSONを使用）
        csv_data = "name,age,city\nAlice,25,London"
        xml_result = mediator.convert("CSV", "XML", csv_data)
        print("CSV to XML (via JSON):")
        print(xml_result)

    except ValueError as e:
        print(f"Error: {e}")
    except Exception as e:
        print(f"Unexpected error occurred: {e}")
```

### コードの説明

1. `DataConverter`抽象クラス: すべてのコンバーターの基底クラスです。
2. 具体的なコンバータークラス: `JSONtoXMLConverter`, `XMLtoJSONConverter`, `CSVtoJSONConverter`が実装されています。
3. `DataConversionMediator`: 変換処理を管理し、適切なコンバーターを選択して実行します。中間形式を指定できるように`convert`メソッドを改善しています。
4. メインの使用例: 各種変換の実行例を示しています。

### 動作確認

このコードを実行すると、以下のような出力が得られます：

```
JSON to XML:
<root><name>John Doe</name><age>30</age><city>New York</city></root>

XML to JSON:
{'name': 'Jane Doe', 'age': '28', 'city': 'San Francisco'}

CSV to XML (via JSON):
<root><name>Alice</name><age>25</age><city>London</city></root>
```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b1945857-d356-3dd7-7ebc-559c61fd40c0.png)

Mediatorパターンを使用することで、複雑なデータ変換システムをシンプルかつ拡張性の高い設計にすることができました。直接的な変換ができない場合でも、中間形式を経由する柔軟な変換が可能になっています。

このパターンを適用することで、新しい変換形式の追加や既存の変換ロジックの修正が容易になり、システムの保守性と拡張性が大幅に向上します。

ただし、小規模なプロジェクトや単純な変換処理では、このパターンの導入がオーバーエンジニアリングになる可能性もあるため、適用する際はプロジェクトの規模と複雑さを十分に考慮する必要があります。

Mediatorパターンは、適切に使用することで複雑なシステムをシンプルに管理できる強力なツールとなります。ぜひ、あなたのプロジェクトでも活用を検討してみてください。