🧸

アダプターパターンを学ぶ

に公開

アダプターパターンを学ぶ

ソフトウェア開発の世界では、「車輪の再発明」を避け、既存の優れたコンポーネントやライブラリをうまく活用することが、効率的で質の高いプロダクト開発の鍵となります。

しかし、現実には様々な壁が立ちはだかります。例えば、

  • 「この外部APIが返すデータ形式、うちのシステムで使っているオブジェクトと微妙に違うな…」
  • 「新しく採用したライブラリのメソッド名が、既存のコードで呼び出している名前と違う…」
  • 「過去に作られた貴重なロジックを再利用したいけど、今の設計と合わない…」

このような「インターフェースの不一致」問題に直面したとき、どうすればよいでしょうか?

一つの方法は、既存のシステムのコードを、新しいライブラリに合わせて大規模に修正することです。しかし、それは多くの手間と時間を要し、新たなバグを生むリスクも伴います。かといって、ライブラリ側のコードを直接書き換えるのは、アップデートの追従が困難になるなど、現実的ではありません。

こんなジレンマを鮮やかに解決してくれるのが、今回ご紹介する「アダプターパターン」です。

アダプターパターンは、まるでコンセントの形状が違う海外で日本の電化製品を使うための「変換アダプター」のように、互換性のないインターフェースを持つクラス同士を協調させるためのデザインパターンです。

この記事では、電源電圧が異なる海外でロボットを動かすというサンプルコードを通して、アダプターパターンの仕組みとメリットを解説します。

登場人物の紹介

アダプターパターンには、主に4つの役割が登場します。今回のサンプルコードでは、それぞれ以下のクラスが対応します。

  • クライアント (Client): Robot クラス
    • 機能を利用する側のクラスです。今回は100Vの電源を必要としています。
  • ターゲット (Target): JapanesePowerOutlet クラス
    • クライアントが利用したいインターフェースを定義しています。今回は supply_100v_power() というメソッドです。
  • アダプティー (Adaptee): AmericanPowerOutlet, ChinesePowerOutlet クラス
    • 既存のクラスで、そのままではクライアントが利用できないインターフェースを持っています。今回は supply_120v_power()supply_200v_power() といったメソッドです。
  • アダプター (Adapter): PowerAdapter クラス
    • アダプティーのインターフェースを、ターゲットのインターフェースに変換する役割を担います。

サンプルコードで学ぶアダプターパターン

それでは、実際のコードを見ていきましょう。

クライアント:Robot.py

ロボットは日本の100V電源で動くように設計されています。そのため、supply_100v_power() メソッドを持つ電源オブジェクトを必要とします。

Robot.py
class Robot():
    def __init__(self, power_outlet):
        self._power_outlet = power_outlet

    def operate(self):
        print("ロボット:動作を開始します。")
        self._power_outlet.supply_100v_power()
        print("ロボット:正常に動作しました。")

Robot クラスは、コンストラクタで渡された power_outlet オブジェクトの supply_100v_power() を呼び出すことしかできません。

ターゲットとアダプティー:PowerOutlets.py

ここでは、日本、アメリカ、中国のコンセントが登場します。

  • JapanesePowerOutlet (ターゲット): ロボットが求める supply_100v_power() を持ちます。
  • AmericanPowerOutlet, ChinesePowerOutlet (アダプティー): それぞれ supply_120v_power(), supply_200v_power() という異なるインターフェースを持ちます。
PowerOutlets.py
class JapanesePowerOutlet:
    def supply_100v_power(self):
        print("日本のコンセント:100Vの電力を供給します。")

class AmericanPowerOutlet:
    def supply_120v_power(self):
        print("アメリカのコンセント:120Vの電力を供給します。")

class ChinesePowerOutlet:
    def supply_200v_power(self):
        print("中国のコンセント:200Vの電力を供給します。")

このままでは、RobotAmericanPowerOutletChinesePowerOutlet を使うことができません。

アダプター:PowerAdapter.py

そこで登場するのが PowerAdapter です。このクラスが、アダプティー(海外のコンセント)をターゲット(日本のコンセント)のインターフェースに適合させます。

PowerAdapter.py
from PowerOutlets import AmericanPowerOutlet, ChinesePowerOutlet

class PowerAdapter:
    def __init__(self, foreign_outlet):
        self._foreign_outlet = foreign_outlet

    def supply_100v_power(self):
        # 1. まずアダプティーのメソッドを呼び出して電圧供給をシミュレートする
        if isinstance(self._foreign_outlet, AmericanPowerOutlet):
            self._foreign_outlet.supply_120v_power()
        elif isinstance(self._foreign_outlet, ChinesePowerOutlet):
            self._foreign_outlet.supply_200v_power()
        else:
            print("エラー:サポートされていない電源です。")
            return
            
        # 2. その後、電圧変換のメッセージを出力する
        print("アダプター:外国の電力を100Vに変換しています...")

PowerAdapter は、Robot が期待する supply_100v_power() メソッドを持っています。その内部では、コンストラクタで受け取った海外のコンセント (_foreign_outlet) の種類に応じて適切なメソッド(supply_120v_power() など)を呼び出し、電圧を変換する処理(ここではメッセージの表示)を行っています。

これにより、Robot から見れば、PowerAdapter はまるで JapanesePowerOutlet のように振る舞います。

実行結果:main.py

main.py を実行すると、アダプターパターンの効果がよくわかります。

main.py
from Robot import Robot
from PowerOutlets import JapanesePowerOutlet, AmericanPowerOutlet, ChinesePowerOutlet
from PowerAdapter import PowerAdapter

# --- 日本で動作させる場合 ---
print("--- 日本での動作 ---")
japanese_power_outlet = JapanesePowerOutlet()
robot_jp = Robot(japanese_power_outlet)
robot_jp.operate()
print("-" * 20)

# --- アメリカで動作させる場合 ---
print("--- アメリカでの動作 ---")
american_power_outlet = AmericanPowerOutlet()
adapter_us = PowerAdapter(american_power_outlet)
robot_us = Robot(adapter_us)
robot_us.operate()
print("-" * 20)

# --- 中国で動作させる場合 ---
print("--- 中国での動作 ---")
chinese_power_outlet = ChinesePowerOutlet()
adapter_cn = PowerAdapter(chinese_power_outlet)
robot_cn = Robot(adapter_cn)
robot_cn.operate()

実行結果

--- 日本での動作 ---
ロボット:動作を開始します。
日本のコンセント:100Vの電力を供給します。
ロボット:正常に動作しました。
--------------------
--- アメリカでの動作 ---
ロボット:動作を開始します。
アメリカのコンセント:120Vの電力を供給します。
アダプター:外国の電力を100Vに変換しています...
ロボット:正常に動作しました。
--------------------
--- 中国での動作 ---
ロボット:動作を開始します。
中国のコンセント:200Vの電力を供給します。
アダプター:外国の電力を100Vに変換しています...
ロボット:正常に動作しました。

アメリカや中国の場合でも、Robot クラスのコードには一切手を加えることなく、PowerAdapter を間に挟むだけで正常に動作させることができました。これがアダプターパターンの力です。

アダプターパターンのメリット

  • 既存コードの再利用: Adaptee となるクラスのソースコードを修正する必要がありません。
  • 疎結合: ClientTarget のインターフェースにのみ依存するため、Adaptee の具体的な実装から切り離されます。
  • 柔軟性の向上: 新しい Adaptee が追加されても、対応する Adapter を作成するだけで、Client 側を変更することなくシステムに組み込めます。

まとめ

アダプターパターンは、異なるインターフェース間のギャップを埋める、非常に強力で実用的なデザインパターンです。ライブラリのバージョンアップでAPIが変わってしまった場合や、外部システムとの連携など、現実の多くの場面で活用できます。

「つなぎ合わせる」というシンプルな発想で、ソフトウェアの柔軟性と再利用性を高めるアダプターパターン。ぜひ覚えておきましょう!

Discussion