ProxyでMixin

オーバーロードするとややこしいことになるな…


追記
これをMixinと呼んでいいのか、ちょっと疑問に思えてきた。
拡張というより、二つのインスタンスを合体させているだけだし…


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 Car {

String light();

String run();

}

public class CarImpl implements Car {

public String light() {
return "car.light";
}

public String run() {
return "car.run";
}

}


public class MixinTest extends TestCase {

public void testMixin() {
Object catCar = Mixin.create(new Object { new CarImpl(), new CatImpl() });

Cat cat = (Cat) catCar;
assertEquals("cat.meow", cat.meow());
assertEquals("cat.run", cat.run());

Car car = (Car) catCar;
assertEquals("car.light", car.light());
assertEquals("cat.run", car.run());

assertEquals("$Proxy0", catCar.getClass().getName());
}

}


public class Mixin {

public static Object create(Object objects) {
Class interfaces = getInterfaces(objects);

InvocationHandler handler = new MixinInvocationHandler(objects);
return Proxy.newProxyInstance(Mixin.class.getClassLoader(), interfaces, handler);
}

private static Class getInterfaces(Object objects) {
Set buffer = new HashSet();

for (int i = 0; i < objects.length; i++) {
Class interfaces = objects[i].getClass().getInterfaces();

for (int j = 0; j < interfaces.length; j++)
buffer.add(interfaces[j]);
}

return (Class) buffer.toArray(new Class[buffer.size()]);
}

}

class MixinInvocationHandler implements InvocationHandler {

private Map objectMap = new HashMap();

private Map methodMap = new HashMap();

public MixinInvocationHandler(Object objects) {
for (int i = 0; i < objects.length; i++) {
Method methods = objects[i].getClass().getMethods();

for (int j = 0; j < methods.length; j++) {
String name = methods[j].getName();
objectMap.put(name, objects[i]);
methodMap.put(name, methods[j]);
}
}
}

public Object invoke(Object proxy, Method method, Object args) throws Throwable {
String name = method.getName();
Object object = objectMap.get(name);

if (object == null)
return null;

Method methodImpl = (Method) methodMap.get(name);

return methodImpl.invoke(object, args);
}

}