🌽

デザインパターン: Adapterパターン -一皮かぶせて再利用-

2024/05/10に公開

はじめに

GoFの23のデザインパターンがまとめられている「Java言語で学ぶデザインパターン入門」を読んでアウトプットとしてデザインパターンを1つずつ記事としてアウトプットしていきます。
原則的にJavaで実装コード例などを記述していきますが、気になったことや改善点、感想等ありましたらぜひコメントくださると嬉しいです!

23のパターン一覧はこちらから ※随時更新中

Adaptorパターンとは

「adapt」とは日本語で「適合させる」と言う意味で、PCなどを電源に接続するときに使われているACアダプターのように提供されているものと必要なものの間に入って、その間を埋めるのがアダプターの仕事です。
プログラムの世界でも、すでに提供されているものがそのまま使えない時に、必要な形に変換してから利用することがよくあります。「すでに提供されているもの」「必要なもの」 の間のずれを埋めるようなデザインパターンがAdapterパターンです。

Adapterパターンのイメージ

2種類のAdapterパターンと実装例

クラスによるAdapterパターン(継承)

電源アダプターの例と対比した表

電源の例 実装例
提供されているもの コンセントからの電流 Humanクラス
変換装置 アダプター PrintHumanクラス
必要なもの PCへの電流 Printインターフェース

クラスによるAdapterパターンでは、既に存在するクラスをそのまま利用するために、インターフェースを持っていないクラスに対して、インターフェースを実装した新しいクラスを作成します。これにより、既存のクラスを別のクラスに変換し、利用することができます。

クラスによるAdapterパターンのクラス図

実装例

Humanクラス
public class Human {
    private String name;
    private int birthday;

    public Human(String name, int birthday){
        this.name = name;
        this.birthday = birthday;
    }
    public void showName(){
        System.out.println(name);
    }
    public void showBirthday(){
        System.out.println(birthday);
    }
}
  • クラス図のAdapteeの部分であらかじめ提供されているクラスとしています。
Printインターフェース
public interface Print {
    public abstract void printName();
    public abstract void printAge();
}
  • Targetの部分で、必要とされているインターフェースとしています。
PrintHumanクラス
public class PrintHuman extends Human implements Print{
    public PrintHuman(String name, int birthday){
        super(name, birthday);
    }

    @Override
    public void printName(){
        showName();
    }

    @Override
    public void printBirthday(){
        showBirthday();
    }
}
  • 本日の主役、Adapterの役割です。
  • 用意されているHumanクラスを拡張(extends)してshowNameとshowBirthdayメソッドを継承しています。
  • 要求されているPrintインターフェースを実装(implements)して、printNameメソッドとprintBirthdayメソッドを実装しています。
Mainクラス
public class Main {
    public static void main(String[] agrs){
        Print p = new PrintHuman("kimura", 20001010);
        p.printName();
        p.printBirthday();
    }
}
  • これらのクラスやインターフェースを利用するmainメソッドです。

実行結果

kimura
20001010

ここでMainクラスの中ではPrintHumanのインスタンスをPrintインターフェースの変数に代入していることに着目してください。 MainクラスはあくまでPrintというインターフェースを使って(つまりprintNameメソッドとprintBirthdayメソッドを使って)、プログラミングしています。HumanクラスにあるshowNameメソッドやshowBirthdayメソッドはMainクラスのソースコートからは隠されています。
最初に例えで出したPCからアダプターを介してコンセントへつながっているとき、PCがアダプターの先を知らないのと似ています。
これがAdapterパターンの機能です。

インスタンスによるAdapterパターン(委譲)

インスタンスによるAdapterパターンは、クラスによるAdapterパターンと異なり、継承による実装ではなく、委譲による実装を行います。クラスによるAdapterパターンと同じように、既存のクラスを再利用するため、Adapterクラスを作成します。ただし、Adapterクラスは、既存クラスを継承しないため、既存クラスと同じインターフェースを持ちます。そして、既存クラスを保持するインスタンス変数を持ち、その変数を利用して、既存クラスのメソッドを呼び出します。

電源アダプターの例と対比した表

電源の例 実装例
提供されているもの コンセントからの電流 Humanクラス
変換装置 アダプター PrintHumanクラス
必要なもの PCへの電流 Printクラス


インスタンスによるAdapterパターンのクラス図
クラスによるAdapterパターンとMainクラス、Humanクラスは同じですが、Printはインターフェースではなくクラスに修正します。

Printクラス
public interface Print {
    public abstract void printName();
    public abstract void printAge();
}
  • Targetの部分をクラスに修正します
PrintHumanクラス
public class PrintHuman extends Print{
    private Human human;
    public PrintHuman(String name, int age){
        human = new Human(name, age);
    }

    @Override
    public void printName(){
        human.showName();
    }

    @Override
    public void printAge(){
        human.showBirthday();
    }
}
  • 1行目ではPrintをextendsしています。
  • 2行目でHumanクラスのインスタンスを保持しています。
    showNameとshowBirthdayの呼び出しをhumanフィールドを経由して呼び出しています。

printNameメソッドやprintBirthdayメソッドが呼ばれたらHumanクラスのインスタンスにお任せしているということです。
実行結果は変わりません。

実行結果

kimura
20001010

Adapterパターンはどんな時に有用か

ライブラリの互換性の確保

既存のライブラリがあるが、そのライブラリを使用するためのインターフェースがアプリケーションにとって望ましくない場合、Adapterパターンを使用することでインターフェースを変換し、ライブラリを使用できるようにすることができます。

例えば、あるアプリケーションでデータをXML形式で出力する必要があり、既存のライブラリであるJSON形式で出力する関数が提供されている場合、Adapterパターンを使用してJSONをXMLに変換することができます。

システムのバージョンアップ

新しいシステムと既存のシステムを連携させる場合、既存のシステムのインターフェースを変更することは困難である場合があります。このような場合に、既存のシステムのインターフェースを新しいシステムが使用するインターフェースに変換するためにAdapterパターンを使用することができます。

例えば、ある企業が新しいシステムを開発し、既存のシステムにある顧客情報を新しいシステムに統合する必要がある場合、既存のシステムの顧客情報を取得するためのAPIが提供されている場合、Adapterパターンを使用してAPIのレスポンスを新しいシステムが使用する形式に変換することができます。

まとめ

Adapterパターンのメリットとデメリット

メリット

  • 既存のクラスを修正することなく新しいインターフェースを実装することで、柔軟性が高く、再利用性が高いプログラムを構築することができます。
  • 異なるインターフェースをもつオブジェクト間で互換性を確保することで、ライブラリの互換性を確保することができます。
  • 新旧システム間の連携を実現できます。

デメリット

  • 過度の使用は、コードの複雑性を増大させる可能性があるため、注意が必要です。

Discussion