Adapter
一言でいうと
Adapter (アダプター、 適合装置) は、 構造に関するデザインパターンの一つで、 非互換なインターフェースのオブジェクト同士の協働を可能とします。
問題
株式市場監視アプリを作成することを想像してみてください。 このアプリは、 複数の情報源から XML 形式の株式データをダウンロードし、 見栄えの良いグラフや図表をユーザーに表示します。
ある時点で、 外部提供の気の利いた分析ライブラリーを統合してアプリを改善することに決めました。 ただし、 一つ問題があります。 分析ライブラリーは、 JSON 形式のデータでのみ機能するということです。
XML で動作するようにライブラリーを変更することもできます。 ただそうすると、 ライブラリーに依存した既存のコードが動かなくなる可能性があります。 さらに、 そもそもライブラリーのソースコードが入手できない可能性があり、 その場合この手法はうまくいきません。
解決策
アダプター を作成します。 これは、 あるオブジェクトのインターフェースを他のオブジェクトが理解できるように変換する特殊なオブジェクトです。
アダプターは、 オブジェクトをラップして、 舞台裏で行われる変換の詳細を隠蔽します。 ラップされたオブジェクトは、 アダプター内で動作しているということすら認識していません。 たとえば、 メートルとキロメートルで動作するオブジェクトを、 すべてのデータをフィートやマイルに変換するアダプターでラップすることができます。
アダプターは、 データの形式変換だけだけではなく、 異なるインターフェースのオブジェクト同士の協働を可能とします。 仕組みは次のとおりです。
- アダプターは、 既存オブジェクトの一つと互換なインターフェースを実装します。
- このインターフェースを使用して、 既存オブジェクトはアダプターのメソッドを安全に呼び出すことができます。
- 呼び出されると、 アダプターはリクエストを二つ目のオブジェクトに渡します。 ただし、 二つ目のオブジェクトが期待する形式で渡します。
時には、 呼び出しを双方向に変換できる双方向アダプターを作成することも可能です。
株式市場アプリの話に戻りましょう。 形式非互換の問題を解決するために、 コードが直接接触する分析ライブラリーのすべてのクラスに対して XML から JSON へのアダプターを作成します。 次に、 これらのアダプターを介してのみライブラリーーと通信するようにコードを調整します。 アダプターは呼び出しを受けると、 着信 XML データを JSON に変換し、 ラップされた分析オブジェクトの適切なメソッドを呼び出します。
現実世界でのたとえ
米国からヨーロッパに初めて旅行する時、 ラップトップを充電しようとすると驚くかもしれません。 電源プラグとソケットの規格は国によって異なるのです。 そのため、 米国のプラグはドイツのソケットに入りません。 この問題は、 アメリカ式ソケットとヨーロッパ式プラグを備えた電源プラグ・アダプターを使用することで解決できます。
構造
オブジェクト・アダプター
この実装では、 オブジェクトの合成原理を使用します。 アダプターは一方のオブジェクトのインタフェースを実装し、 もう一方のオブジェクトをラップします。 これは、 一般的に普及しているすべてのプログラミング言語で実装可能です。
-
クライアント (Client) は、 既存のビジネス・ロジックを含んだクラスです。
-
クライアント・インターフェース (Client Interface) は、 クライアント側コードと協働するためには従わなければいけないプロトコル=決まり事を記述しています。
-
サービス (Service) は、 外部から提供されるか、 昔から使われてきた、 ある役に立つクラスです。 このクラスは、 非互換のインターフェースのため、 クライアントから直接使うことができません。
-
アダプター (Adapter) は、 クライアントとサービスの両方と機能できるコードです: クライアント・インターフェースを実装すると同時にサービス・オブジェクトをラップします。 アダプターは、 アダプターのインターフェースを介してクライアントから呼び出され、 それをラップされたサービス・オブジェクトが理解できる形式に変換して呼び出します。
-
クライアント側コードは、 クライアント・インタフェースを介してアダプターとやりとりする限り、 具象アダプター・クラスと結合されることはありません。 このおかげで、 新しい種類のアダプターをプログラムに導入しても既存のクライアント側コードは問題なく動作します。 これは、 サービス・クラスのインターフェースを変更したり置き換えたりする際に便利です。 クライアント側コードを変更することなく、 新しいアダプター・クラスを作成することができます。
クラス・アダプター
この実装では継承を利用します。 アダプターは、 両方のオブジェクトのインターフェースを同時に継承します。 この方法は、 C++ のような多重継承をサポートするプログラミング言語でのみ実装可能であることにご注意ください。
-
クラス・アダプター では、 オブジェクトをラップする必要はありません。 クライアントとサービスの両方から振る舞いを継承するからです。 適合 (変換) は、 上書きされたメソッドの間で行われます。 完成したアダプターは、 既存のクライアント側クラスの代わりに使うことができます。
擬似コード
この Adapter パターンの使用例は、 矛盾の典型である 「丸い穴に四角いくい」 (訳注: 合わないものを無理に一緒にすることを表現する英語の慣用句) に基づいています。
アダプターは、 半径が四角の対角線の半分の長さ (言い換えると、 四角いくいを含むことのできる最小の円の半径) である丸いくいのふりをします。
適応性
Adapter クラスは、 既存のクラスを使用したいが、 そのインターフェースが自分のコードの他の部分と互換性がない場合に使用します。
Adapter パターンでは、 自分のクラスと、 昔から引き継がれてきたクラスや外部提供のクラスや奇妙なインターフェースのクラスとの間で翻訳者として機能する中間層のクラスを作成します。
既存のいくつかのサブクラスを再利用したいが、 スーパークラスに追加できる共通機能が欠けている場合に、 このパターンを使用します。
実装方法
-
最低二つの非互換なクラスがあることを確認してください:
- 有益だが変更不能なサービスクラス。 (多くの場合、 外部提供だから、 過去から引き継がれたコードだから、 あるは、 依存する要素の数が多すぎるためといった理由で変更できない。)
- サービス・クラスを利用したい 1 個以上の クライアント・クラス。
-
クライアント・インタフェースを宣言し、 クライアントとサービスとの情報伝達の方法を記述する。
-
クライアント・インターフェースに従うアダプター・クラスを作成。 メソッドはとりあえず空のままにする。
-
アダプター・クラスに、 サービス・オブジェクトへの参照を格納するためのフィールドを追加します。 このフィールドは、 コンストラクタで初期化するのが一般的な方法ですが、 アダプターのメソッドの呼び出し時に渡す方が便利な場合もあります。
-
クライアント・インタフェースの全メソッドをアダプター・クラスで一つずつ実装していってください。 アダプターは、 実際の仕事のほとんどはサービス・オブジェクトに委ね、 インターフェースやデータ形式の変換だけを行うようにします。
-
クライアントは、 クライアント・インタフェースを介してアダプターを使用する必要があります。 そうすることで、 クライアントのコードに影響を与えることなくアダプターの変更や拡張が可能となります。
長所と短所
- 単一責任の原則。 インターフェースやデータ変換のコードを、 プログラムの主要なビジネス・ロジックから分離可能。
- 開放閉鎖の原則。 クライアント・インターフェースを介してアダプターと連携する限り、 既存のクライアント側コードを壊すことなく、 新しい種類のアダプターをプログラムに追加可能。
- 一連の新規のインターフェースとクラスを追加する必要があるため、 全体的なコードの複雑性が増加。 コードの他の部分と一致するようにサービス・クラスを書き直す方が単純でいい場合あり。
他のパターンとの関係
-
Bridge は通常、 アプリケーションの部分部分を独立して開発できるように、 設計当初から使われます。 一方、 Adapter は、 既存のアプリケーションに対して利用され、 本来は互換性のないクラスとうまく動作させるために使われます。
-
Adapter は既存のオブジェクトのインターフェースを変更するのに対し、 Decorator はインターフェースの変更なしにオブジェクトを強力にします。 さらに、 Decorator は、 再帰的な合成をサポートします。 これは、 Adapter を使用する時には不可能です。
-
Adapter はラップされたオブジェクトに対しては異なるインターフェースを提供し、 Proxy は同じインターフェースを提供し、 Decorator は強化したインターフェースを提供します。
-
Facade が既存のオブジェクトに対して新しいインターフェースを定義するのに対し、 Adapter は既存のインターフェースを使えるようにしようとするものです。 Adapter は通常一つのオブジェクトだけを包み込みますが、 Facade は複数のオブジェクトからなるサブシステム全体を相手にします。
-
Bridge、 State、 Strategy (と限られた意味合いでは、 Adapter も) は、 非常に似た構造をしています。 実際のところ、 これらの全てのパターンは、 合成に基づいており、 仕事を他のオブジェクトに委任します。 しかしながら、 違う問題を解決します。 パターンは、 単にコードを特定の方法で構造化するためのレシピではありません。 パターンが解決する問題に関して、 開発者同士がするコミュニケーションの道具でもあります。