デザインパターン Adapterパターンについて勉強した

Adapterパターンとは

adapte(アダプター)と聞いて何を思い浮かべるでしょうか。
代表的なものの一つにACアダプターがあります。
ACアダプターはコンセントから供給される100vの交流電流を5vの直流電流に変換したりするものです。

adapterパターンではシグネチャAのメソッドで用意されている機能をシグネチャBで呼び出したいような時に使用します。
簡単に例えると、コンソール出力に毎回System.out.println("hogehuga")を書くと長くなるから新たにprint(Object)メソッドを作るといったイメージです。

public void print(Object object){
    System.out.println(object);
}

シグネチャAの機能を使うのにシグネチャBを呼び出す。シグネチャBからシグネチャAを呼び出す。
このようにシグネチャAの前に挟まれたシグネチャBがアダプター(変換器)の役割を果たします。

コード例1(継承

adapterパターンでは継承を使用するものと委譲を使用するものの2パターンが存在します。
まずは継承の例から見ていきます。

引数に"年","月","日"を入力すると"yyyy年mm月dd日"の形式でコンソール出力をするprintaDateメソッドを持つPrinterクラスが既に作られているものとします。
そして上記のメソッドと同じ機能を持ったprintCalendarメソッドをMyCalendarクラスで実装したい場合を考えます。

Printerクラス

public class Printer{

	public void printDate(int year,int month,int day) {
		System.out.println(String.format("%d年%d月%d日",year,month,day));
	}
}

入力された年月日を特定のフォーマットでコンソール出力するメソッドを持っています。


CalendarInterfaceインタフェース

public interface CalendarInterface {

	void printCalendar(int year,int month,int day);
}

printCalendarはyear(年),month(月),day(日)を渡すと何かしらのフォーマットに整えてコンソール出力することを期待して宣言されています。


MyCalendarクラス

public class MyCalendar extends DefaultCalendar implements CalendarInterface{

	@Override
	public void printCalendar(int year, int month, int day) {
		printDate(year,month,day);
	}
}

CalendarInterfaceで宣言されたprintCalendarメソッドを実装しています。
printCalendarメソッドの実装では、親クラスのprintDateメソッドに全て処理を任せています。

これだけで考えると「最初からPrintクラスのprintDateメソッドを呼ぶか、PrintクラスにCalendarInterfaceを実装すればいいのではないか?MyCalendarクラスは不要ではないか?」と思うかもしれません。
しかし、例えばPrintクラスとCalendarInterfaceインタフェースが既に作られているフレームワークであったりした場合、そのように変更することはできません。

MyCalendarはPrintクラスとCalendarInterfaceインタフェースの橋渡し役として実装されているのです。

実装例1(継承

public class Main {

	public Main() {
		CalendarInterface calendar = new MyCalendar();

		calendar.printCalendar(2021, 6, 12);
	}

	public static void main(String[] args) {
		new Main();
	}
}

MyCalendarクラスをインスタンス化し、printCalendarメソッドを呼び出しています。
calendarの型がCalendarInterfaceになっているのは、仮にCalendarInterfaceを実装したDefaultCalendarクラスに置き換えたくなった時でも、即座に書き換えられるようにするためです。

実行してみます。

2021年6月12日

コード例2(委譲

もう一つ、委譲を利用するパターンについても説明してみます。

DefaultCalendarクラス

public class DefaultCalendar implements CalendarInterface{

	private Print print = new Print();

	@Override
	public void printCalendar(int year, int month, int day) {
		print.printDate(year,month,day);
	}
}

こちらはMyCalendarと若干似ていますが、Printクラスを継承せず、代わりに委譲をしています。

実装例2(委譲

public class Main {

	public Main() {
		//CalendarInterface calendar = new MyCalendar();
		CalendarInterface calendar = new DefaultCalendar();

		calendar.printCalendar(2021, 6, 12);
	}

	public static void main(String[] args) {
		new Main();
	}
}

コンストラクタ1行目では、実装例1で使用したMyCalendarクラスがコメント化されています。
そして2行目では同じ変数名でDefaultCalendarのインスタンスが作られています。

2021年6月12日

実装例1と同じ結果が表示されました。

特徴

Printクラスで実装されているメソッドをCalendarInterfaceインタフェースで宣言されているメソッドで呼び出したい。
しかしPrintクラスにCalendarInterfaceインタフェースを実装することはできない。と言った場面を例に解説してみました。

結構基本的で、誰もが知らないうちに使っているようなパターンではないかと思います。

補足

実装例2ではコンストラクタ1行目で、実装例1で使用したMyCalendarクラスがコメント化されています。
そして2行目では同じ変数名でDefaultCalendarのインスタンスが作られています。
変数の型をCalendarInterfaceにすることで、CalendarInterfaceを実装したクラスであれば簡単に置き換えることが可能になっています。