はじめに
皆さん、こんにちは!PS-SLの織田です。今回は、『Java言語で学ぶデザインパターン入門』の第2章を読んだ感想をまとめていきたいと思います。第1章ではIteratorパターンを扱いましたが、第2章ではAdapterパターンについて書かれています。第1章のブログはコチラからご覧ください。それでは詳しく見ていきましょう!
Adapterパターンとは
Adapterパターンとは「すでに提供されているもの」と「本当に必要なもの」のズレを埋めるためのパターンです。文字で説明しても何のこっちゃという感じなので、具体的なコードとともに説明していきます。
すでに提供されているもの:Banner.java
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 + "*");
}
}
このクラスは「すでに提供されているもの」を表現します。文字列を受け取り、その文字列を括弧で囲んで表示するshowWithParen()メソッドと、アスタリスクで囲んで表示するshowWithAster()メソッドを提供します。前提として、このクラスには若干修正したい内容(言語ごとに表示内容を変えたい)があるのですが、下手に触るとバグが発生する恐れがあるため、変更することなくそのまま使用したい既存の資産という位置づけです。
本当に必要なもの Print.java
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
このインターフェースは「こういう形で使いたい」という理想的な仕様を定義します。弱い表示を行うprintWeak()メソッドと、強い表示を行うprintStrong()メソッドという抽象メソッドを宣言しています。これはあくまで仕様の定義にすぎないため、具体的な実装は書かれていません。実装は次のクラスで表現されます。
橋渡し役:PrintBanner.java
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
@Override
public void printWeak() {
// 言語設定に応じて表示を変更
if (isJapanese()) {
System.out.println("《");
showWithParen();
System.out.println("》");
} else {
showWithParen(); // 日本語以外では既存処理
}
}
@Override
public void printStrong() {
// 言語設定に応じて表示を変更
if (isJapanese()) {
System.out.println("《");
showWithAster();
System.out.println("》");
} else {
showWithAster(); // 日本語以外では既存処理
}
}
このクラスがAdapterパターンの核心部分です。BannerクラスとPrintインターフェースを橋渡しする役割を担います。「日本語とその他の言語で表示方法を切り替えたい!」というニーズに応えつつ、もとの関数は一切修正していません。
printWeak()メソッドの実装では継承したshowWithParen()メソッドを、printStrong()メソッドの実装では継承したshowWithAster()メソッドを呼び出します。これにより、既存のBannerクラスの機能をPrintインターフェースの形で利用できるように変換しています。
元のshowWithParen()やshowWithAster()のコードを修正しなくても、ニーズに合わせた微調整を行うことができています。
利用例:Main.java
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
このクラスはAdapterパターンを利用するクライアント側の実装例です。重要なポイントは、Print型の変数を宣言し、PrintBannerのインスタンスを代入している点です。
クライアントコードからはBannerクラスの存在は完全に隠蔽されており、Printインターフェースのメソッドのみを使用してBannerクラスの機能を利用しています。この設計により、将来的にBannerクラス以外の実装に変更したい場合でも、クライアントコードは全く変更する必要がありません。
Adapterパターンで嬉しいこと
「元のコードを修正しないで良いのは分かったけど、インターフェースを作る必要はないんじゃない?」と思う人がいるかもしれません。しかし、このパターンを使うことで受けられる恩恵があります。例えば、現在は単純な文字列を出力していますが、将来的に「HTMLで出力するクラス」を使いたくなったとします。こうなると、「複数のファイルにまたがって修正を実施しなきゃいけないのか…」と思うかもしれませんが、実は新しいクラスとアダプターを追加すれば大丈夫で、Mainを修正する必要はほぼありません。
新しいクラス:HtmlDisplay.java
public class HtmlDisplay {
private String content;
public HtmlDisplay(String content) {
this.content = content;
}
public void showAsEmphasis() {
System.out.println("<em>" + content + "</em>");
}
public void showAsStrong() {
System.out.println("<strong>" + content + "</strong>");
}
}
新しいアダプター:HtmlPrintBanner.java
public class HtmlPrintBanner extends HtmlDisplay implements Print {
public HtmlPrintBanner(String string) {
super(string);
}
@Override
public void printWeak() {
showAsEmphasis();
}
@Override
public void printStrong() {
showAsStrong();
}
}
AdapterパターンなしのMain.java
public class Main {
public static void main(String[] args) {
// 変更前:Bannerを直接使用
// Banner banner = new Banner("Hello");
// banner.showWithParen();
// banner.showWithAster();
// 変更後:HtmlDisplayに変更
HtmlDisplay html = new HtmlDisplay("Hello");
html.showAsEmphasis(); // メソッド名も変更が必要
html.showAsStrong(); // メソッド名も変更が必要
}
}
AdapterパターンありのMain.java
public class Main {
public static void main(String[] args) {
// 変更前
Print p = new PrintBanner("Hello");
// 変更後:この1行だけ変更すれば済む
Print p = new HtmlPrintBanner("Hello");
// 以下のコードは全く変更不要
p.printWeak();
p.printStrong();
}
}
Adapterパターンあり:クライアントコード上の修正を最小限に抑えられる
Adapterパターンなし:より多くの変更を実施する必要がある
また、もっと複雑な処理をする際にはより重宝します。例えば表示方法を動的に切り替える際を考えてみましょう。
AdapterパターンありのMain.java
public class Main {
public static void main(String[] args) {
// 設定ファイルや環境変数で切り替え可能
String outputType = System.getProperty("output.type", "banner");
Print p;
switch(outputType) {
case "html":
p = new HtmlPrintBanner("Hello");
break;
case "xml":
p = new XmlPrintBanner("Hello");
break;
default:
p = new PrintBanner("Hello");
}
// どの実装でも同じコードで動作
p.printWeak();
p.printStrong();
}
}
AdapterパターンなしのMain.java
public class Main {
public static void main(String[] args) {
String outputType = System.getProperty("output.type", "banner");
// 各クラスのインスタンスを別々に管理する必要がある
Banner banner = null;
HtmlDisplay htmlDisplay = null;
XmlDisplay xmlDisplay = null;
switch(outputType) {
case "html":
htmlDisplay = new HtmlDisplay("Hello");
break;
case "xml":
xmlDisplay = new XmlDisplay("Hello");
break;
default:
banner = new Banner("Hello");
}
// 弱い表示の処理 - 全パターン分岐が必要(インターフェースでメソッド名を統一していない弊害)
switch(outputType) {
case "html":
htmlDisplay.showAsEmphasis();
break;
case "xml":
xmlDisplay.displayAsItalic();
break;
default:
banner.showWithParen();
}
// 強い表示の処理 - また全パターン分岐が必要
switch(outputType) {
case "html":
htmlDisplay.showAsStrong();
break;
case "xml":
xmlDisplay.displayAsBold();
break;
default:
banner.showWithAster();
}
}
}
Adapterパターンなしの場合には、インターフェースによるメソッド名の統一がなされていないため、呼び出すクラスに応じてメソッドの名前を別々に表記する必要があります。そのため、クライアントコードは複雑かつ長大になってしまっています。
まとめ
Adapterパターンの本質は、「変更に強いシステム」の実現にあります。既存のコードを活かしながら、新しい要求に柔軟に対応できる設計を提供します。特に重要なのは、インターフェースによるメソッド名の統一です。これにより、開発者は複雑な実装詳細を意識することなく、一貫した方法でシステムを操作できるようになります。
実際の開発では、「既存のコードは変更したくないが、新しい方法で使いたい」という場面が発生するかと思います。そんな時、Adapterパターンは既存システムの価値を最大限に活かしながら、将来の拡張性も確保する理想的な解決策となるのです。

