0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本記事は、GoFデザインパターン解説シリーズの1つです。

今回から構造に関するパターンに入ります。

最初に紹介するのはAdapter(アダプター)パターンです。

解決したい課題

「既存のクラスをそのまま使いたいが、呼び出し側が期待するインターフェース(メソッド名・引数の形)と一致しない」という場面があります。

典型的なのは、外部ライブラリやレガシーコードなど、自分では中身を修正できないクラスを、自分のアプリケーションのインターフェースに合わせて使いたいケースです。

例えば、自社アプリではPaymentProcessorという独自インターフェースで決済処理を統一しているところに、外部の決済ライブラリLegacyPaymentGatewayを導入したいとします。

しかし、このライブラリのメソッド名・引数の形式は自社のPaymentProcessorとは全く異なり、そのままでは差し替えられません。

Adapterパターンは、間に「変換役」のクラスを1つ挟むことで、インターフェースの不一致を吸収することでこの問題を解決します。

電源プラグの形状変換アダプターと同じ発想です。

image.png

クラス構成

  • Target(ターゲット): 呼び出し側が期待する、標準のインターフェース
  • Adaptee(アダプティ): 既存の、インターフェースが異なるクラス(変換したい対象)
  • Adapter(アダプター): Targetを実装しつつ、内部でAdapteeを呼び出して変換する仲介クラス

image.png

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つ追加するだけで、利用側のコードには一切手を入れずに済みます。

image.png

継承を使う実装(クラスアダプター)との違い

Javaでは上記のように「Adapteeをフィールドとして持ち、内部で委譲する」実装(オブジェクトアダプター)が一般的ですが、他の言語では多重継承を使ってAdapteeを継承しつつTargetも実装するクラスアダプターという実装方法も紹介されます。

Javaはクラスの多重継承ができないため、基本的にはオブジェクトアダプター(コンポジション、委譲)による実装が採用されます。

使用場面

  • 外部ライブラリやSDKのインターフェースが、自社アプリの標準インターフェースと一致しない場合
  • 古いバージョンのAPI(レガシーコード)を、新しいインターフェース設計のまま利用したい場合
  • 単体テストにおいて、外部依存クラスをテスト用のダミー実装に差し替えやすくしたい場合(TargetインターフェースへのAdapterを挟むことで、モック化がしやすくなる)
  • 複数の類似ライブラリ(例: 複数の決済代行会社のSDK)を、統一したインターフェースで扱いたい場合

まとめ

項目 内容
解決する課題 インターフェースが異なる既存クラスを、修正せずに呼び出し側の期待する形で利用したい
実現方法 Targetインターフェースを実装するAdapterクラスを用意し、内部でAdapteeに処理を委譲する
メリット 既存クラスを変更せず、呼び出し側のコードも変更せずに、両者を繋ぐことができる
Java実装 多重継承がないため、委譲によるオブジェクトアダプターが一般的

おすすめ書籍

GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。

Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]

※本リンクはアフィリエイトリンクを含みます。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?