連載
» 2005年04月20日 10時00分 公開

JavaTips 〜Javaプログラミング編:Adapterパターンを使い利用コンポーネントを切り替える

[佐藤匡剛,@IT]

 システムが大規模になるほど、あるいは優れた設計が施されたシステムほど、システムは高度にコンポーネント化される傾向にあります。例えばトランザクション管理、ロギング、コネクション・プーリングなど、機能単位でコンポーネント化されます。

 さて、開発中のシステムの機能の一部を、あるコンポーネントを用いて実現していた際、性能上の理由などから、他のベンダより提供されている同じ機能をもったコンポーネントに切り替える必要が生じたとしましょう。しかし、提供元が異なるコンポーネントは、利用するためのAPI(一連のメソッド呼び出しなど)に互換性がないことがほとんどです。システムの構築は進行してしまっているので、コンポーネントを切り替えるにはコードを大幅に書き換える必要が生じます。このような問題に直面した場合の良い解決方法はないものでしょうか?

 デザインパターンは、こうした問題に直面したときに威力を発揮します。ここでは、「Adapterパターン」が問題を解決してくれます。

Adapterパターンのサンプル

 開発中のシステムSampleSystemでは、“ICompInterface”インターフェイスを持つコンポーネントを利用していたとします。このインターフェイスを実装したコンポーネントに性能上の問題があり、同等の機能をもった新しいコンポーネント“NewComp”を導入しなければならなくなったとします。しかし、以下に示すコードで分かるように、ICompInterfaceとNewCompには、利用方法に互換性がありません。

リスト1 SampleSystem.java
package net.mogra.wings.javatips;
public class SampleSystem {
  public static void main(String[] args) {
    // OldCompは性能上問題のあった古いコンポーネント。
    // このサンプルコードは、あえて示さない

    ICompInterface comp = new OldComp();
    comp.start();
    comp.stop();
  }
}


リスト2 ICompInterface.java
package net.mogra.wings.javatips;
public interface ICompInterface {
  void start();
  void stop();
}


リスト3 NewComp.java
package net.mogra.wings.javatips;
public class NewComp {
  public void init() {
    /* システム初期化 */
  }
  public void run() {
    /* システム起動 */
  }
  public void finish() {
    /* システム終了 */
  }
}


 このICompInterfaceとNewCompとの間に「アダプタ」クラスを差し込むことによって、インターフェイスを適応させる方法が、Adapterパターンです。Adapterパターンは、このアダプタクラスの作り方によって、2通りの方法に分かれます。

(1) クラスに適用するアダプタ

 コンポーネントをサブクラス化し、そこで適応させたいインターフェイスを実装することでアダプタを作成する方法です(リスト4)。サブクラス化の際にメソッドをオーバーライドすることで、コンポーネントの挙動をカスタマイズできる、というのが特徴です。しかし、こちらは、コンポーネントのクラスがfinal宣言されていたり、適合させたい対象がインターフェイスでなく抽象クラスだった場合には、採用できません。

リスト4 NewCompClassAdapter.java
package net.mogra.wings.javatips;
public class NewCompClassAdapter extends NewComp implements
  ICompInterface {
  public void start() {
    init();
    run();
  }
  public void stop() {
    finish();
  }
}


(2) オブジェクトに適用するアダプタ

 適応させたいインターフェイスを実装し、クラス内にあるコンポーネントのインスタンスへ処理を委譲させるアダプタを作成する方法です(リスト5)。こちらの方が、より柔軟性をもったアプローチです。メソッドをオーバーライドしてコンポーネントの挙動をカスタマイズしたいなどの理由がなければ、こちらを採用するのが良いでしょう。

リスト5 NewCompObjectAdapter.java
package net.mogra.wings.javatips;
public class NewCompObjectAdapter implements ICompInterface {
  NewComp comp = new NewComp();
  public void start() {
    comp.init();
    comp.run();
  }
  public void stop() {
    comp.finish();
  }
}


 最後に、作成したアダプタクラスをリスト6のようにしてSampleSystemシステムへ組み込みます(赤字が修正部分)。下の例は(2)のアダプタを用いた場合ですが、(1)の場合でも修正方法は同様です。

リスト6 SampleSystem.java(抜粋)
 public static void main(String[] args) {
    ICompInterface comp = new NewCompObjectAdapter();
    comp.start();
    comp.stop();
  }


 これで、元のシステムへの修正は、コンポーネントの実装を切り替えるたった1行の修正で済みました。このサンプル程度の小さなプログラムでは、「そんなものか」と思うかもしれませんが、何十ものクラスでコンポーネントが使われているような大規模なプログラムを考えてみて下さい。Adapterパターンの効果がはっきりするはずです。

 また、ここからはAdapterパターンが扱う領域ではありませんが、リスト6の赤字部分のコンストラクタ呼び出しを、以下のように特定のメソッドで置き換えるのも良い方法です。

  ICompInterface comp = ComponentFactory.getComponent();


 コンストラクタが複数箇所で直接呼ばれていると、そのすべてを修正しない限りコンポーネントを完全に切り替えられません。しかし、こうしておけば、ComponentFactoryクラスを修正するだけでコンポーネントの切り替えができます。こうした方法は、非常によく採られる方法です。

Adapterパターンのクラス図

 Adapterパターンを用いたコードのクラス図は、一般に以下のようになります。ここで、上のサンプルコードにおけるSampleSystem、ICompInterface、NewComp、NewCompClassAdapter(NewCompObjectAdapter)は、それぞれClient、ITarget、Adaptee、Adapterに相当します。

クラスに適用するアダプタ クラスに適用するアダプタ
オブジェクトに適用するアダプタ オブジェクトに適用するアダプタ

実際にAdapterパターンが使われている例:Jakarta Commons Logging

 Adapterパターンは、身近な所でも採用されています。Apache JakartaのCommons Loggingがその典型的な例です。Commons Loggingは、Javaのさまざまなロギング実装を共通のインターフェイスで利用できるようにするライブラリです。コアAPIのロギングや、Log4Jなど、利用方法がばらばらのロギング実装を、Commons LoggingのLogインターフェイスを元に一元的に操作できるのです。

 上のクラス図で示した各クラスと、Commons Loggingにおける各クラスとの対応関係は以下の通りです。

クラス図 Commons Loggingのクラス
Client Commons Loggingを利用するクラス(ユーザが作成する部分
ITarget Logインターフェイス
Adaptet Jdk14Logger、Log4JLoggerなど
Adaptee 個々のロギング実装。コアAPIのロギング(java.util.logging.Logger)やLog4J(org.apache.log4j.Logger)など

Profile

WINGSプロジェクト

佐藤匡剛


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。