ProxyでMixin その2

インスタンスを拡張するという意味でMixinできなかな?と思ったので、いくつかアイデア。


たとえば、汎用的なtoString()を追加する。

ターゲットはこんな感じ。


public interface Cat {
String meow();
String run();
}

public class CatImpl implements Cat {
public String meow() { return "cat.meow"; }
public String run() { return "cat.run"; }
}

拡張用のモジュールはこんな感じ。


public interface Module {
public void setBody(Object body);
public boolean defined(Method method);
}

public class ToStringModule implements Module {

private Object body = null;

public void setBody(Object body) { this.body = body; }

public boolean defined(Method method) {
return "toString".equals(method.getName());
}

public String toString() {
return "クラス名: " + body.getClass().getName() + " / ハッシュ値: " + body.hashCode();
}

}

で、テスト。


public class MixinTest {

public static void main(String args) {
Cat cat = (Cat) Mixin.create(new CatImpl(), new ToStringModule());
System.out.println(cat.meow());
System.out.println(cat.toString());
}

}


cat.meow
クラス名: bar.CatImpl / ハッシュ値: 19770577

仕掛けはこんな感じ。


public class Mixin {

public static Object create(Object body, Module module) {
InvocationHandler handler = new MixinInvocationHandler(body, module);
return Proxy.newProxyInstance(Mixin.class.getClassLoader(), body.getClass().getInterfaces(), handler);
}

}

class MixinInvocationHandler implements InvocationHandler {

private Object body = null;

private Module module = null;

public MixinInvocationHandler(Object body, Module module) {
this.body = body;
this.module = module;
module.setBody(body);
}

public Object invoke(Object proxy, Method method, Object args) throws Throwable {
if (module.defined(method))
return method.invoke(module, args);
else
return method.invoke(body, args);
}

}

toString()のようなメソッドでない限り、追加するメソッドのインターフェースを新しく作る必要があるのが、ちょっとメンドい。
バイトコードをいじれると、もう少し楽なのかな?


追記
でも、RubyのMixinてクラス定義時にやっているような。
動的な機能の拡張はMixinとは呼ばないかなぁ…うーん…