はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回から構造に関するパターンに入ります。
最初に紹介するのはAdapter(アダプター)パターンです。
解決したい課題
「既存のクラスをそのまま使いたいが、呼び出し側が期待するインターフェース(メソッド名・引数の形)と一致しない」という場面があります。
典型的なのは、外部ライブラリやレガシーコードなど、自分では中身を修正できないクラスを、自分のアプリケーションのインターフェースに合わせて使いたいケースです。
例えば、自社アプリではPaymentProcessorという独自インターフェースで決済処理を統一しているところに、外部の決済ライブラリLegacyPaymentGatewayを導入したいとします。
しかし、このライブラリのメソッド名・引数の形式は自社のPaymentProcessorとは全く異なり、そのままでは差し替えられません。
Adapterパターンは、間に「変換役」のクラスを1つ挟むことで、インターフェースの不一致を吸収することでこの問題を解決します。
電源プラグの形状変換アダプターと同じ発想です。
クラス構成
- Target(ターゲット): 呼び出し側が期待する、標準のインターフェース
- Adaptee(アダプティ): 既存の、インターフェースが異なるクラス(変換したい対象)
- Adapter(アダプター): Targetを実装しつつ、内部でAdapteeを呼び出して変換する仲介クラス
Javaでの実装例
Target:自社アプリが期待するインターフェース
public interface PaymentProcessor {
void pay(int amountYen);
}
Adaptee:インターフェースが異なる、既存の外部ライブラリ(想定)
// 修正できない外部ライブラリのクラスという想定
public class LegacyPaymentGateway {
// 引数の単位がドル(int cents)で、メソッド名も異なる
public void executeTransaction(double amountUsdCents) {
System.out.println("[LegacyGateway] " + amountUsdCents + "セントの決済を実行");
}
}
Adapter:インターフェースの差を吸収する変換役
public class PaymentGatewayAdapter implements PaymentProcessor {
private final LegacyPaymentGateway legacyGateway;
private static final double YEN_TO_USD_RATE = 0.0067; // 想定レート
public PaymentGatewayAdapter(LegacyPaymentGateway legacyGateway) {
this.legacyGateway = legacyGateway;
}
@Override
public void pay(int amountYen) {
// 円 → USDセントへの変換や、メソッド名の違いをここで吸収する
double amountUsdCents = amountYen * YEN_TO_USD_RATE * 100;
legacyGateway.executeTransaction(amountUsdCents);
}
}
利用側のコード
public class Main {
public static void main(String[] args) {
LegacyPaymentGateway legacyGateway = new LegacyPaymentGateway();
PaymentProcessor processor = new PaymentGatewayAdapter(legacyGateway);
// 利用側は、常にPaymentProcessorインターフェースだけを意識すればよい
processor.pay(1000);
}
}
実行結果:
[LegacyGateway] 670.0セントの決済を実行
利用側のコードはPaymentProcessor#pay(int)という自社の標準インターフェースしか知りません。
LegacyPaymentGateway特有のメソッド名や単位の違いは、PaymentGatewayAdapterの内部に閉じ込められています。
将来、別の決済サービスに乗り換える場合も、新しいAdapterクラスを1つ追加するだけで、利用側のコードには一切手を入れずに済みます。
継承を使う実装(クラスアダプター)との違い
Javaでは上記のように「Adapteeをフィールドとして持ち、内部で委譲する」実装(オブジェクトアダプター)が一般的ですが、他の言語では多重継承を使ってAdapteeを継承しつつTargetも実装するクラスアダプターという実装方法も紹介されます。
Javaはクラスの多重継承ができないため、基本的にはオブジェクトアダプター(コンポジション、委譲)による実装が採用されます。
使用場面
- 外部ライブラリやSDKのインターフェースが、自社アプリの標準インターフェースと一致しない場合
- 古いバージョンのAPI(レガシーコード)を、新しいインターフェース設計のまま利用したい場合
- 単体テストにおいて、外部依存クラスをテスト用のダミー実装に差し替えやすくしたい場合(TargetインターフェースへのAdapterを挟むことで、モック化がしやすくなる)
- 複数の類似ライブラリ(例: 複数の決済代行会社のSDK)を、統一したインターフェースで扱いたい場合
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | インターフェースが異なる既存クラスを、修正せずに呼び出し側の期待する形で利用したい |
| 実現方法 | Targetインターフェースを実装するAdapterクラスを用意し、内部でAdapteeに処理を委譲する |
| メリット | 既存クラスを変更せず、呼び出し側のコードも変更せずに、両者を繋ぐことができる |
| Java実装 | 多重継承がないため、委譲によるオブジェクトアダプターが一般的 |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


