Python アダプターパターン
アダプターパターンは構造型デザインパターンの一つで、互換性のないインターフェース間の連携を可能にします。現実世界の電源アダプターが異なる国の電化製品のプラグをローカルのコンセントに接続できるように、プログラミングにおいてもアダプターパターンは互換性のない二つのインターフェース間の橋渡し役を担います。
コアコンセプト
アダプターパターンは主にインターフェースの不一致の問題を解決します。既存のクラスを使用する必要があるが、そのインターフェースが要件に合わない場合に、アダプターパターンが役立ちます。中間層(アダプター)を作成してインターフェースを変換することで、元々互換性のないクラスが連携できるようにします。
パターン構造
アダプターパターンには次の3つの主要な役割があります:
- ターゲットインターフェース(Target):クライアントが期待するインターフェース
- アダプター(Adapter):ターゲットインターフェースを実装し、アダプティーをラップする
- アダプティー(Adaptee):適応される必要がある既存のクラス
なぜアダプターパターンが必要か
実際の問題シナリオ
想像してみてください。既存のログシステムがあり、特定の方法でログを記録しています:
例
def write_log(self, message):
print(f"[LEGACY] {message}")
しかし今度は新しいログインターフェースを使いたいとします:
例
def log(self, level, message):
print(f"[{level.upper()}] {message}")
アダプターがない場合、古いログシステムを呼び出しているすべてのコードを修正する必要があり、時間がかかる上にエラーが発生しやすくなります。
アダプターの利点
- コードの再利用:既存のクラスをソースコードを変更せずに再利用できる
- 疎結合:インターフェース変換ロジックをビジネスロジックから分離する
- 柔軟性:異なる実装を簡単に切り替えられる
- 開放/閉鎖の原則に準拠する:拡張に対して開き、修正に対して閉じている
アダプターパターンの実装方法
クラスアダプター(継承を使用)
クラスアダプターは多重継承によってインターフェース適応を実現します:
例
"""適応される旧ログクラス"""
def write_log(self, message):
print(f"[LEGACY] {message}")
class NewLoggerInterface:
"""ターゲットインターフェース"""
def log(self, level, message):
pass
class LoggerAdapter(NewLoggerInterface, LegacyLogger):
"""クラスアダプター - 継承によって実装"""
def log(self, level, message):
# 新しいインターフェースを旧インターフェースに変換する
formatted_message = f"{level}: {message}"
self.write_log(formatted_message)
# 使用例
logger = LoggerAdapter()
logger.log("INFO", "システム起動成功")
logger.log("ERROR", "データベース接続失敗")
出力結果:
[LEGACY] INFO: 系统启动成功 [LEGACY] ERROR: 数据库连接失败
オブジェクトアダプター(コンポジションを使用)
オブジェクトアダプターはコンポジションによって実装されます。これがより一般的な方法です:
例
"""適応される側の旧決済システム"""
def process_payment(self, amount_in_dollars):
print(f"支払い処理: ${amount_in_dollars}")
return True
class NewPaymentInterface:
"""新しい決済インターフェース"""
def pay(self, amount, currency="CNY"):
pass
class PaymentAdapter(NewPaymentInterface):
"""オブジェクトアダプター - コンポジションによって実装"""
def __init__(self, legacy_payment):
self.legacy_payment = legacy_payment
def pay(self, amount, currency="CNY"):
# 通貨変換ロジック
if currency == "CNY":
amount_in_dollars = amount / 7.0 # 為替レート変換を簡略化
else:
amount_in_dollars = amount
# 旧システムのインターフェースを呼び出し
return self.legacy_payment.process_payment(amount_in_dollars)
# 使用例
legacy_payment = LegacyPayment()
payment = PaymentAdapter(legacy_payment)
# 新しいインターフェースを使用して旧システムを呼び出す
payment.pay(700, "CNY") # 700元(人民元)を支払う
payment.pay(100, "USD") # 100ドルを支払う
出力結果:
处理支付: $100.0 处理支付: $100
実際の応用例
例1:データ形式アダプター
例
class XMLData:
"""XMLデータソース"""
def get_xml_data(self):
return """
<users>
<user>
<name>山田</name>
<age>25</age>
</user>
<user>
<name>佐藤</name>
<age>30</age>
</user>
</users>
"""
class JSONProcessor:
"""JSONプロセッサ(JSON形式を期待)"""
def process_json(self, json_data):
data = json.loads(json_data)
for user in data['users']:
print(f"名前: {user['name']}, 年齢: {user['age']}")
class XMLToJSONAdapter:
"""XMLからJSONへのアダプター"""
def __init__(self, xml_data):
self.xml_data = xml_data
def convert_to_json(self):
# 簡略化されたXMLからJSONへの変換
xml_content = self.xml_data.get_xml_data()
# 実際のアプリケーションでは、ここでxml.etree.ElementTreeなどのライブラリを使用して解析する必要があります
json_data = {
"users": [
{"name": "山田", "age": 25},
{"name": "佐藤", "age": 30}
]
}
return json.dumps(json_data, ensure_ascii=False)
# アダプターを使用
xml_source = XMLData()
adapter = XMLToJSONAdapter(xml_source)
json_processor = JSONProcessor()
json_data = adapter.convert_to_json()
json_processor.process_json(json_data)
出力結果:
姓名: 张三, 年龄: 25 姓名: 李四, 年龄: 30
例2:サードパーティサービスアダプター
例
"""WeChat Pay SDK"""
def wechat_pay(self, amount, user_id):
print(f"WeChat Pay: ユーザー{user_id}が{amount}元を支払い")
return {"status": "success", "transaction_id": "wx_123456"}
class AliPay:
"""Alipay SDK"""
def alipay(self, money, user_account):
print(f"Alipay: アカウント{user_account}が{money}元を支払い")
return {"code": 200, "trade_no": "ali_789012"}
class UnifiedPaymentAdapter:
"""統一決済アダプター"""
def __init__(self, payment_service):
self.payment_service = payment_service
def pay(self, amount, user_info):
# 統一決済インターフェース
if isinstance(self.payment_service, WeChatPay):
return self.payment_service.wechat_pay(amount, user_info)
elif isinstance(self.payment_service, AliPay):
return self.payment_service.alipay(amount, user_info)
else:
raise ValueError("サポートされていない支払い方法")
# クライアントコードは統一的に呼び出せる
def process_order(payment_adapter, amount, user_info):
"""注文支払いを処理"""
result = payment_adapter.pay(amount, user_info)
print(f"支払い結果: {result}")
return result
# 使用例
wechat_pay = WeChatPay()
ali_pay = AliPay()
wechat_adapter = UnifiedPaymentAdapter(wechat_pay)
ali_adapter = UnifiedPaymentAdapter(ali_pay)
# 統一された呼び出し方
process_order(wechat_adapter, 100, "user_001")
process_order(ali_adapter, 200, "user_002")
出力結果:
微信支付: 用户user_001支付100元
支付结果: {'status': 'success', 'transaction_id': 'wx_123456'}
支付宝: 账户user_002支付200元
支付结果: {'code': 200, 'trade_no': 'ali_789012'}
アダプターパターンのベストプラクティス
1. 適切な実装方法を選ぶ
| 実装方法 | 適用シーン | 利点 | 欠点 |
|---|---|---|---|
| クラスアダプター | アダプティークラスが少なく、ターゲットインターフェースが単純な場合 | 実装が簡単で、コード量が少ない | 多重継承が必要で、クラス階層が複雑になる可能性がある |
| オブジェクトアダプター | ほとんどのシーン、特に複数のクラスを適応する必要がある場合 | より柔軟で、コンポジション優先の原則に合致する | アダプターオブジェクトの作成が必要 |
2. アダプターの単一責任を保つ
例
class DataFormatAdapter:
"""データ形式変換のみを担当"""
def convert_format(self, data):
pass
# 悪いプラクティス:責任が多すぎる
class BadAdapter:
"""形式変換、検証、キャッシュなどを同時に処理"""
def convert_format(self, data):
pass
def validate_data(self, data):
pass
def cache_data(self, data):
pass
3. インターフェース抽象を使用する
例
class PaymentInterface(ABC):
"""決済インターフェース抽象"""
@abstractmethod
def pay(self, amount, user_info):
pass
class LoggerInterface(ABC):
"""ログインターフェース抽象"""
@abstractmethod
def log(self, level, message):
pass
よくある問題と解決策
問題1:アダプターが多すぎて複雑さが増す
解決策:ファクトリーパターンを使用してアダプターを作成する
例
"""アダプターファクトリー"""
@staticmethod
def create_payment_adapter(service_type):
if service_type == "wechat":
return UnifiedPaymentAdapter(WeChatPay())
elif service_type == "alipay":
return UnifiedPaymentAdapter(AliPay())
else:
raise ValueError(f"サポートされていない支払いタイプ: {service_type}")
# ファクトリーを使用してアダプターを作成
wechat_adapter = AdapterFactory.create_payment_adapter("wechat")
ali_adapter = AdapterFactory.create_payment_adapter("alipay")
問題2:パフォーマンスの考慮
解決策:遅延ロードとキャッシュを実装する
例
"""キャッシュ付きアダプター"""
def __init__(self, adaptee):
self.adaptee = adaptee
self._cache = {}
def process_data(self, key):
if key not in self._cache:
# 時間のかかる操作をシミュレート
result = self.adaptee.expensive_operation(key)
self._cache[key] = result
return self._cache[key]
実践演習
演習1:ファイル形式アダプターの作成
ファイル形式アダプターを実装して、CSV形式のデータをJSON形式に変換してください:
例
"""CSVファイルリーダー"""
def read_csv(self, filepath):
# CSVファイルの読み込みをシミュレート
return [
["name", "age", "city"],
["Alice", "25", "Beijing"],
["Bob", "30", "Shanghai"]
]
class JSONExporter:
"""JSONエクスポーター"""
def export_json(self, data):
import json
print("JSONデータをエクスポート:")
print(json.dumps(data, indent=2, ensure_ascii=False))
# ここでCSVToJSONAdapterクラスを実装してください
# あなたのコード...
# テストコード
csv_reader = CSVReader()
json_exporter = JSONExporter()
# アダプターを作成してテスト
adapter = CSVToJSONAdapter(csv_reader)
json_data = adapter.convert_to_json()
json_exporter.export_json(json_data)
演習2:設計を考える
次のシナリオを考慮して、アダプターパターンをどのように使用しますか:
- あなたのアプリケーションは複数のデータベース(MySQL、PostgreSQL、SQLite)をサポートする必要がありますが、各データベースの接続方法が異なります。
- 複数のサードパーティAPIを統合する必要がありますが、それらの認証方式と返却形式はそれぞれ異なります。
- あなたのシステムは複数のメッセージキュー(RabbitMQ、Kafka、Redis)をサポートする必要があります。
そのうちの1つのシナリオに対して、アダプターパターンの解決策を設計してください。
まとめ
アダプターパターンはインターフェースの非互換性問題を解決する効果的なツールです。この記事の学習を通じて、以下を習得する必要があります:
- アダプターパターンの基本概念と適用シナリオ
- クラスアダプターとオブジェクトアダプターの実装方法
- 実際のプロジェクトでの応用テクニック
- アダプターパターンのベストプラクティスと注意事項
覚えておいてください、アダプターパターンの核となる考え方は「変換」であり「修正」ではありません。既存のコードを変更せずに新しい機能を統合するのに役立ち、大規模システムを維持する際の重要な設計パターンです。
実際の開発では、アダプターパターンを適切に使用することで、コードをより柔軟で保守しやすく、拡張しやすくすることができます。
その他の拡張