Adapter(Wrapper)パターン [Java]

GoFデザインパターンについて学んでます.

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

  • 作者:結城 浩
  • 発売日: 2004/06/19
  • メディア: 大型本

前回から2か月経ってた😇そろそろちゃんとやりませうか…
xrdnk.hateblo.jp

今回は「Adapter」パターンについて学びます.
Adapterパターンというより,Wrapper(ラッパー)パターンの方が馴染みがあると思う.

Adapter(Wrapper) Pattern

現実世界でのアダプターを想像すれば早いです.

過去の思い出を語ります.小学生低学年の頃はフィリピンにいました.
日本の電圧は100ボルトですが,フィリピンの電圧は220ボルトです.
幼かったためかアダプターの概念を知らずにいたため,一回日本の電化製品をそのまま
フィリピンのコンセントにぶち込んだら,煙がもくもく出て電化製品がぶっ壊れたのを鮮明に覚えてます.
ボルトが通常の2倍かかって負荷に耐えられなかったからですね.そこでアダプターの役割を知りました.

「既に提供されているもの」と「必要なもの」の間の「ずれ」を埋めるデザインパターンです.

Adapter パターン - Wikipedia

Adapter パターンを用いると、既存のクラスに対して修正を加えることなく、インタフェースを変更することができる。Adapter パターンを実現するための手法として継承を利用した手法と委譲を利用した手法が存在する。

継承(is-a)を利用したサンプルプログラム

与えられた文字列を (Hello) のように表示したり,*Hello*のように表示するサンプルプログラムです.
後述の委譲を利用した版も同様です.

Bannerクラスでは文字列をパレンティスで囲って表示するshowWithParen()
文字列をアスタリスクで囲って表示するshowWithAster()があります.
Bannerクラスは「既に提供されているもの」だと仮定します.

一方,Printインタフェースでは,文字列を弱く表示するprintWeak()
強く表示するprintStrong()が用意されてます.Printは「必要なもの」だと仮定します.

やりたいことは,Bannerクラスを使って,Printインタフェースを満たすクラスを作ること.
その役を担うクラスをPrintBannerクラスとしています.

PrintBannerクラスをAdapter(Wrapper)クラスと呼ばれます.

UML

f:id:xrdnk:20200322082506p:plain

パッケージ名は"InheritanceAdapter"としてます.
Packageは1つしか登場してないのでUMLではパッケージの記載を省略.

Bannerクラス

package InheritanceAdapter;

/**
 * Adaptee:適合させる側の役
 */
public class Banner {

    private String string;

    // コンストラクタ
    public Banner(String string) {
        this.string = string;
    }

    // パレンティスで囲って表示
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }

    // アスタリスクで囲って表示
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}
package InheritanceAdapter;

/**
 * Target : 対象の役
 */
public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}

PrintBanner クラス

package InheritanceAdapter;

/**
 * Adapter : 適合させる側の役
 */
public class PrintBanner extends Banner implements Print {

    public PrintBanner(String string) {
        super(string);
    }

    // showWithParen()を継承し,printWeak()に実装
    @Override
    public void printWeak() {
        showWithParen();
    }

    // showWithAster()を継承し,printStrong()に実装
    @Override
    public void printStrong() {
        showWithAster();
    }

}

Main クラス

package InheritanceAdapter;

/**
 * Client : 依頼者役
 */
public class Main {
    public static void main(String[] args) {
        // NOTE : AdapteeではなくAdapterを利用している
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

委譲(has-a)を利用したサンプルプログラム

UML

f:id:xrdnk:20200322084245p:plain

BannerクラスとMainクラスは同じですがパッケージ名を"DelegateAdapter"にしてます.
Packageは1つしか登場してないのでUMLではパッケージの記載を省略.

継承版では自分の基底クラスから継承したshowWithParen(),showWithAster()を呼び出してましたが,
今度はフィールド経由で呼び出してます.別のインスタンス(Bannerインスタンス)のメソッドに任せてます.

package DelegateAdapter;

/**
 * Target : 対象の役
 */
public abstract class Print {
    public abstract void printWeak();
    public abstract void printStrong();
}

PrintBannerクラス

package DelegateAdapter;

/**
 * Adapter : 適合させる側の役
 */
public class PrintBanner extends Print {
    private Banner banner;

    public PrintBanner(String string) {
        this.banner = new Banner(string);
    }

    @Override
    public void printWeak() {
        banner.showWithParen();
    }

    @Override
    public void printStrong() {
        banner.showWithAster();
    }
}

使いどころとか

すでに存在しているクラスを利用してプログラミングをすることが殆ど.
そのクラスが十分な品質を保っているならば,そのクラスを部品として再利用するもの.
Adapter(Wrapper)パターンは既存クラスに一皮かぶせて必要とするクラスを作り,
必要とすてメソッド群を素早く作ることができる.
もしバグが出ていたとしても,既存クラス(Adaptee)はバグがないことがわかるため,
Wrapperクラスを重点的に調べればよくなり,プログラムのチェックが楽になる.

新しいAPIにAdaptさせようとするときに,Adapteeをいじって修正してしまうと,
動作テストが終わっているAdapteeクラスを修正後にもう一度テストしないといけなくなる.

また,ソフトウェアのバージョンアップ時に互換性を考慮する必要がある.
古い版と新しい版を共存させて楽にメンテナンスをするためにAdapterパターンが役に立つこともある.

終わりに

結構無意識にWrapperしてる人が多いんじゃなかろうか.
継承版の方が綺麗だけれど,疎結合な作りを考えると委譲版を使うといいかもしれない…
ただ,is-aにするか,has-aにするかはケースバイケースとしか言いようがないよね.

オブジェクト指向によるクラス設計 | Think IT(シンクイット)

参考資料等

デザインパターン「Adapter」 - Qiita

Adapter パターン (互換性のないインタフェースを持つクラス同士の接続を可能にする) — WTOPIA v1.0 documentation