単純に委譲しないメソッドのみを定義したアレをProxyクラスで何とかする

id:S_a_k_uとの話の中で、「DI的な感じで委譲させるのはいいけど委譲のコード書くのがやたら面倒だよね」という論点において合意に至り、Proxyで何とかなりそうだよねという話になったので、何とかしてみた。

コメントとか書いてません。悪しからず。

test

TDDの練習ということで、テストケースから。

import static org.junit.Assert.*;

import org.junit.Test;

public class XTest {
    final A a = new A();
    
    @Test
    public void test() {
        X b = new B(a);
        assertEquals("A", b.name());
        assertEquals(21, b.age());
        
        X proxy = XProxy.create(X.class, a, new C(a));
        assertEquals("A", proxy.name());
        assertEquals(22, proxy.age());
        
    }
    
    @Test(expected=XException.class)
    public void testThrowingException() throws Exception {
        X d = XProxy.create(X.class, a, new D());
        d.exception();
    }
}

interface X {
    public String name();
    public int age();
    public void exception() throws XException;
}

class XException extends Exception {
    private static final long serialVersionUID = 1L;
}

class A implements X {
    public String name() {return "A";}
    public int age() {return 20;}
    public void exception() {};
}

class B implements X {
    private X x;
    public B(X x) {this.x = x;}
    public String name() {return x.name();}
    public int age() {return x.age() + 1;}
    public void exception() {};
}

class C {
    private X x;
    public C(X x) {this.x = x;}
    public int age() {return x.age() + 2;}
}

class D {
    public void exception() throws XException {throw new XException();};
}

何とかするオブジェクトの生成器

import java.lang.reflect.*;

public class XProxy {
    @SuppressWarnings("unchecked")
    public static <T> T create(Class<T> mockClass, InvocationHandler handler) {
        Class<?> proxyClass = Proxy.getProxyClass(mockClass.getClassLoader(), mockClass);
        try {
            return (T)proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public static <T> T create(Class<T> mockClass, final T x, final Object forwardable) {
        return XProxy.create(mockClass, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                try {
                    Class<?>[] parameterTypes;
                    if (args != null) {
                        parameterTypes = new Class<?>[args.length];
                        for (int i = 0; i < args.length; i++) parameterTypes[i] = args.getClass();
                    } else {
                        parameterTypes = null;
                    }
                    return forwardable.getClass().getMethod(method.getName(), parameterTypes).invoke(forwardable, args);
                } catch (InvocationTargetException e) {
                    throw e.getCause();
                } catch (NoSuchMethodException e) {
                    return method.invoke(x, args);
                }
            }
        });
    }
}

FIXME

  • create(Class mockClass, final T x, final Object forwardable)で、NoSuchMethodExceptionが起きまくる件について、forwardableのメソッドリストをあらかじめ生成しておいて、比較するとか。
    • ってか、"boolean hasMethod"メソッド、なんでないんだろ…。