動的なインターフェイスの追加(擬似的な方法)
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 パターンと言うらしい。