Adapterパターン


 ★★☆

Adapterパターン

二つのオブジェクトの間に入って、インターフェースを合わせる

adaptという単語は日本語で「適合させる」という意味で、adapterとは「適合させるもの」という意味になります。Adapterパターンは、インタフェースに互換性の無いクラス同士を組み合わせることを目的としたパターンです。


適用方法として以下の3つがあります。

 インターフェース変換

例えば、これまで利用していたメソッドと同じ機能を、よりすぐれた形で提供するメソッドを持つクラスの存在を知ったとします。しかし、このすぐれたメソッドは、これまで利用していたメソッドとは異なるインタフェースを持つため、乗り換えるとなると多大な変更を余儀なくされる場合があります。こんなとき、この2つのメソッドのインタフェースの違いを吸収してやる Adapter を準備することで、少ない変更で新しいメソッドに乗り換えることができるのです。

インターフェース補完

親クラスもしくはインターフェースを継承する場合、その全抽象メソッドを実装しなければなりません。そのとき、親子関係の間に入って全抽象メソッドのデフォルトの機能を持ったメソッドを実装し、そのクラスでは必要なメソッドだけ実装すればいいようにします。

protectedメソッドを使用可能にする

対象オブジェクトのメソッドが protected だったとしても、対象オブジェクトを継承したサブクラスで publicメソッドとして提供します。



すでに作られたクラスがあって、新しいインターフェースに適合させると考えると、つい既存のクラスのソースをいじって「修正」しようと考えてしまいます。でもそれでは、動作確認がされている既存のクラスを修正後にもう一度テストしなければならなくなってしまいます。

Adapterパターンは、既存のクラスにはまったく手を加えずに、目的のインターフェースにあわせようとするものです。また、 Adapterパターンでは、既存のクラスのソースが必ずしも必要ではありません。既存のクラスの仕様だけわかれば、新しいクラスを作ることができるのです。


また、古い版と新しい版とを共存させているときに、新しい版のみ改修したくなった場合も、新しい版のクラスを使って古い版のメソッドを実装するAdapter役のクラスを作ればよいのです。

例題

Bracket
  getAngleBracketString()
  getDoubleAngleBracketString()

与えられた文字列 AAAAA を「AAAAA」として表示する printArticleName、『AAAAA』として表示する printBookName を持つクラスを、IPrint インタフェース(C++ の場合は、純粋仮想関数のみを持つ Print 抽象クラス)にあわせて作りなさい。

ただし、IPrint インタフェース(C++ では Print 抽象クラス)にはあっていないが、すでに同様の処理をする Bracket クラスがあります。


実行結果
「Hello」
『Hello』






既存クラス、インタフェース

Bracket.java
public class Bracket { private String message; public Bracket(String message) { this.message = message; } public String getAngleBracketString() { return "「" + message + "」"; } public String getDoubleAngleBracketString() { return "『" + message + "』"; } }
IPrint.java
public interface IPrint { public void printArticleName(); public void printBookName(); }

Adapterパターンを使用しない例

PrintQuotedBook.java
public class PrintQuotedBook implements IPrint { private String message; public PrintQuotedBook(String message) { this.message = message; } public void printArticleName() { System.out.println("「" + message + "」"); } public void printBookName() { System.out.println("『" + message + "』"); } }
Main.java
public class Main { public static void main(String[] args) { PrintQuotedBook p = new PrintQuotedBook("Hello"); p.printArticleName(); p.printBookName(); } }

Adapterパターンを使用した例

PrintQuotedBook.java
public class PrintQuotedBook extends Bracket implements IPrint { public PrintQuotedBook(String message) { super(message); } public void printArticleName() { System.out.println(getAngleBracketString()); } public void printBookName() { System.out.println(getDoubleAngleBracketString()); } }
Main.java
public class Main { public static void main(String[] args) { PrintQuotedBook p = new PrintQuotedBook("Hello"); p.printArticleName(); p.printBookName(); } }

同様の表示をする Bracket クラスがありますが使っていません。新たに同様な機能を作成しています。

簡単な処理ならこれでも良いかもしれませんが、少し複雑なものになると労力が無駄になりますし、果たして同じ処理なのかどうかも疑わしいこともあるでしょう。

ここでは、Bracket クラスを継承して、その中で定義されているメソッドを呼び出しています。

これなら、Bracket クラスが変更された場合でも、別途 PrintQuotedBook クラスで変更しなくても自動的に変更されることになります。

この例では継承を用いて実現しましたが、委譲(お任せ)を使って実現することもできます。

継承するのではなく、Bracket クラスのインスタンスを生成して、Bracket クラス内のメソッドを呼び出すわけです。

PrintQuotedBook.java
public class PrintQuotedBook implements IPrint { private Bracket bracket; public PrintQuotedBook(String message) { bracket = new Bracket(message); } public void printArticleName() { System.out.println(bracket.getAngleBracketString()); } public void printBookName() { System.out.println(bracket.getDoubleAngleBracketString()); } }

Java では、継承したスーパークラスのメソッドを隠蔽することはできません。先の例では、PrintQuotedBook を利用している main から Bracket のメソッドである getAngleBracketString() なども見えてしまうわけです。

しかし、後の例のような委譲にすれば PrintQuotedBook で定義したメソッドしか使用できません。


この例では、メソッドが大変小さいものですから、既存のクラスを再利用しなくても記述できます。しかし、その規模が大きく、かつ充分にテストされているとしたら再利用したいものです。Adapter パターンは、既存のクラスに一皮かぶせて必要とするクラスを作ります。