動的なインターフェイスの追加(擬似的な方法)

public interface Foo {
 void foo();
}

public class FooImpl implements Foo {
 public void foo() {
  // ...
 }
}

ある、Foo という仕様と同時に、Fooに対する実装を提供したとします。普通ですね。

ここで「Fooはプラガブル*1にしたい。実装によって「動的に*2」新しい機能を追加することができるようにしたい。」と考えたとします。

public interface Foo extends ExtenderA, ExtenderB ... {
 void foo();
}

こんなイメージ。Fooのインターフェイスを「動的に」追加したい。しかし、これはJavaの言語仕様上不可能*3なことです。

拡張したければ、Fooを継承して新たなインターフェイスを作るしかありません。

public interface ExtenderA extends Foo {
 void extA();
}

public class ExtenderAImpl extends FooImpl implements ExtenderA {
 public void extA() {
  // ...
 }
}
public interface ExtenderB extends Foo {
 void extB();
}

public class ExtenderBImpl extends FooImpl implements ExtenderB {
 public void extB() {
  // ...
 }
}

しかし、拡張者が多数いて、お互いの拡張者を意識せずに、このAPIの使用者が「複数の拡張を同時に使用したい」という、更に特殊なケースを想定してみます。

ExtendedFooA と ExtendedFooB を作った人は、お互いの拡張の存在を知らず、APIのユーザが、foo, extA, extB の3機能を全て同時に使いたい、というケースです。ややこしいですかねw

Foo foo = ...;
foo.foo();
((ExtenderA) foo).extA();
((ExtenderB) foo).extB();

API使用者側は、こんな感じで3つのAPIを使いたい。さてどうしようか。

こんな状況になると、継承による拡張は不可能になります。

この状況の打開策の一つ*4が、Eclipse APIにも採用されている IAdaptable インターフェイスの考え方です。

まず、API使用者側のコードから。

Foo foo = ...;
foo.getAdapter(ExtenderA.class).extA();
foo.getAdapter(ExtenderB.class).extB();

何をどうするとこのような事が可能になるのか。まず Adaptable インターフェイスを定義します。

public interface Adaptable {

 <T> T getAdapter(Class<T> clazz);

 void registerAdapter(Object adapter);
}

そして、Fooの仕様策定者は、以下のようにFooの仕様と実装を提供します。

public interface Foo extends Adaptable {
 void foo();
}

public class FooImpl implements Foo {

 private List<Object> adapters = new ArrayList<Object>();

 public void foo() {
  // ...
 }

 public <T> T getAdapter(Class<T> clazz) {
  for (Object adapter : adapters) {
   if (clazz.isAssignableFrom(adapter.getClass())) {
    return (T) adapter;
   }
  }
  return null;
 }

 public void registerAdapter(Object adapter) {
  adapters.add(adapter);
 }
}

こうすることにより、拡張者はお互いを意識することなく Foo を自由に拡張することができるようになります。

public interface ExtenderA {
 void extA();
}

public class ExtenderAImpl implements ExtenderA {
 public void extA() {
  // ...
 }
}

// ExtenderBは、上記とほぼ同じなので省略。

こうすることにより、どこかのタイミング*5で、

foo.registerAdapter(new ExtenderAImpl());

としておく事により、foo は ExtenderA による拡張が行われます。ExtenderBも同様。

擬似的にですが、ExtenderAとExtenderBは、互いに依存せず、Fooを動的に拡張できました。めでたしめでたし。

おまけ

public interface ExtenderA extends Foo {
 void extA();
}

public class ExtenderAImpl extends FooImpl implements ExtenderA {
 public void extA() {
  // 前処理...
  this.foo();
  // 後処理...
 }
}

こんな事を行いたい場合、どうすればいいでしょうか? 上記例の解決方法では、FooとExtenderAは完全に独立してしまった為、拡張側からFooのAPIにアクセスすることができません。

これは、こうしちゃえばいいんじゃないかな。

public class ExtenderAImpl implements ExtenderA {

 private Foo foo;

 public ExtenderAImpl(Foo foo) {
  this.foo = foo;
 }

 public void extA() {
  // 前処理...
  foo.foo();
  // 後処理...
 }
}
foo.registerAdapter(new ExtenderAImpl(foo));

ちょっとマニアックだったかなw

追記 2008/12/29

Extension Object パターンと言うらしい。

*1:pluginで拡張可能。

*2:つまり、再コンパイル無しで。

*3:バイトコードいじれば別だけど…w

*4:といっても一つしか思いつきませんがw

*5:FooImplのインスタンスが生成された直後が望ましい。