daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

Javaでデザインパターンを学ぶ:Adapterパターン

Javaによるデザインパターンの8回目です!

今回も教材は、参考文献の「Java言語で学ぶデザインパターン入門第3版」のサンプルコードを使わせて頂きます。

今回は、Adapterパターンです。Adapterとは適合させるという意味です。既存のクラスを必要なインタフェースに適合させるイメージです。

では、やっていきましょう!

参考文献

参考文献のサンプルプログラムのダウンロード

www.hyuki.com

はじめに

「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。

Javaでデザインパターンの記事一覧

先に、23種類のデザインパターンを示します。

参考サイト:デザインパターン (ソフトウェア) - Wikipedia

デザインパターン一覧
◆生成に関するパターン
 ・Abstract Factory:関連するインスタンスを状況に応じて、適切に生成する方法を提供する
 ・Builder:複合化されたインスタンスの生成過程を隠蔽する。
 ・Factory Method:生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
 ・Prototype:同様のインスタンスを生成するために、原型のインスタンスを複製する
 ・Singleton:あるクラスについて、インスタンスが1つしか存在しないことを保証する
◆構造に関するパターン
 ・Adapter:元々関連性のない2つのクラスを接続するクラスを作る
 ・Bridge:クラスと呼び出し側の間の橋渡しをするクラスで、実装を隠蔽する
 ・Composite:再帰的な構造を表現する
 ・Decorator:あるインスタンスに対し、動的に付加機能を追加する
 ・Facade:複数のサブシステムの窓口となる共通のインタフェースを提供する
 ・Flyweight:多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
 ・Proxy:共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる
◆振る舞いに関するパターン
 ・Chain of Responsibility:イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されていくようにする
 ・Command:複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切り替えを実現する
 ・Interpreter:構文解析のために、文法規則を反映するクラス構造を作る
 ・Iterator:複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する
 ・Mediator:オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする
 ・Memento:データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする
 ・Observer:インスタンスの変化を他のインスタンスから監視できるようにする
 ・State:オブジェクトの状態を変化させることで、処理内容を変えられるようにする
 ・Strategy:データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切り替えを容易にする
 ・Template Method:アルゴリズムは抽象クラスで、処理内容はサブクラスで定義する
 ・Visitor:データ構造を保持するクラスと、それに対して処理を行うクラスを分離する

エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は、「Adapterパターン」をやっていきます!

サンプルコードの理解

Adapterパターンは、元々関連性のない2つのクラスを接続するクラスを作る、とあります。既存のクラスを、新しく要求されたインタフェースに対して、書き換えるのではなく、既存のクラスを継承して、新しいインタフェースに適合したクラスを作って対応させるイメージです。

それでは、早速サンプルコードを見ていくことにします。

今回のPlantUMLのクラス図

PlantUMLで作成したクラス図のコードと画像ファイルです。

新しく要求されたインタフェースのPrintインタフェースと、既存のクラスがBannerクラスです。新しいインタフェースに適合させたAdapterパターンを適用したのがPrintBannerクラスです。

Mainクラス(使う側のクラス)はPrintインタフェースを知るだけで、他の2つのクラスのことは知らなくていいところが特徴なので、Mainクラスも明示しています。

@startuml
interface Print {
    + {abstract} void printWeak()
    + {abstract} void printStrong()
}
Main --> Print : Uses >

class Banner {
    - String string
    + void showWithParen()
    + void showWithAster()
}

class PrintBanner {
    + void printWeak()
    + void printStrong()
}
Print <|. PrintBanner : implements <
PrintBanner -|> Banner : extends >
@enduml

今回のクラス図
今回のクラス図

Mainクラス(使う側のクラス)

まずは、今回の実装のイメージが沸くように、先にMainクラスを見ていきます。また、実行結果も合わせて示します。

PrintBannerクラスのインスタンスをPrintクラスの変数に格納して、printWeak()printStrong() をコールしています。

printWeak() は、"Hello"という文字列を括弧で装飾するメソッドで、printStrong() は、強調のアスタリスクで装飾するメソッドです。

public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

実行結果

実行結果
実行結果

もう少し中身を見ていきます。

Printインタフェース

Printインタフェースは、2つの抽象メソッドを定義しています。

public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}

Bannerクラス

Bannerクラスは、過去に既に実装されたと仮定したクラスです。

プライベートメンバ変数として、string を持ちます。

showWithParen() は、括弧で装飾するメソッドです。

showWithAster() は、アスタリスクで強調を装飾するメソッドです。

新しく要求されたPrintインタフェースに対して、Bannerクラスは、機能としては十分なメソッドを持っていますが、インターフェースは合いません。そこで、次に説明するAdapterパターンを実装したPrintBannerクラスが必要になります。

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 + "*");
    }
}

PrintBannerクラス

Adapterパターンを実現した PrintBannerクラスは、Printインタフェースを実装したクラスであり、かつ、Bannerクラスを継承したクラスです。

Printインタフェースの2つの抽象メソッドを実装しています。

printWeak() は、Bannerクラスの showWithParen() の機能を呼び出しています。

printStrong() は、Bannerクラスの showWithAster() の機能を呼び出しています。

Printインタフェースに合わせて、既存のBannerクラスをうまく適合させているところが、Adapterパターンの特徴です。

public class PrintBanner extends Banner implements Print {
    public PrintBanner(String string) {
        super(string);
    }

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

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

Adapterパターンのメリットと考察

Adapterパターンは、過去に実装とテストを行い、実績のあるクラスを、新しく要求されたインターフェースに適合させる方法を提供してくれます。

既存のクラスを使いまわすことは、よくあると思います。そのとき、既存のクラスを変更して対応してしまうと、既存のクラスの信頼性はなくなり、テストのやり直しが発生してしまいます。せっかく既存のクラスを使いまわしているのに、テストする時間を多く必要とするのでは、もったいないですね。

そういう場合に、Adapterパターンを適用すると、既存のクラスを全く変更せずに対応することができます。

適合させたクラス(今回のPrintBannerクラス)のテストは必要になりますが、インタフェースを適合させただけのクラスなので、既存のクラスに比べると、規模の小さいクラスになり、テストの時間も少なくて済むでしょう。

使いまわせそうなクラスを見つけたときに、すぐに変更を始めるのではなく、しっかり考えて対応することが重要ということですね。そういうケースで、落ち着いて、Adapterパターンを思い出すことが難しいですね(笑)。

今回は以上です!

おわりに

今回はAdapterパターンを学びました。

最後までお読みいただき、ありがとうございました。