Вызов общих методов без общего интерфейса - PullRequest
2 голосов
/ 23 апреля 2019

У меня есть какой-то сгенерированный код (то есть его нельзя изменить), который выглядит примерно так:

class Generated1 {
    public String getA() {
        return "1";
    }

    public void setB(String b) {
    }

    public void setC(String c) {
    }

    public void setD(String d) {
    }
}

class Generated2 {
    public String getA() {
        return "2";
    }

    public void setB(String b) {
    }

    public void setC(String c) {
    }

    public void setD(String d) {
    }
}

Я исследую эти объекты с помощью отражения. Ни один из них не реализует какой-либо общий интерфейс, но их много, и я хочу относиться к ним так, как будто они реализуют:

interface CommonInterface {
    String getA();

    void setB(String b);

    void setC(String c);

    void setD(String d);
}

Это, конечно, должно быть возможно. Это считается совершенно хорошим кодом

class CommonInterface1 extends Generated1 implements CommonInterface {
    // These are perfectly good classes.
}

class CommonInterface2 extends Generated2 implements CommonInterface {
    // These are perfectly good classes.
}

Полагаю, я ищу что-то вроде:

private void doCommon(CommonInterface c) {
    String a = c.getA();
    c.setB(a);
    c.setC(a);
    c.setD(a);
}

private void test() {
    // Simulate getting by reflection.
    List<Object> objects = Arrays.asList(new Generated1(), new Generated2());
    for (Object object : objects) {
        // What is the simplest way to call `doCommon` with object here?
        doCommon(object);
    }
}

Мой вопрос: как мне обращаться с объектом, который не implement и interface, но на самом деле имеет весь код для этого, как если бы он реализовывал интерфейс.

Я хочу заменить

private void doCommon(Generated1 c) {
    String a = c.getA();
    c.setB(a);
    c.setC(a);
    c.setD(a);
}

private void doCommon(Generated2 c) {
    String a = c.getA();
    c.setB(a);
    c.setC(a);
    c.setD(a);
}

...

с

private void doCommon(CommonInterface c) {
    String a = c.getA();
    c.setB(a);
    c.setC(a);
    c.setD(a);
}

Я знаю, что могу использовать Proxy, как это, но я бы действительно предпочел использовать что-то лучше.

private void test() {
    // Simulate getting by reflection.
    List<Object> objects = Arrays.asList(new Generated1(), new Generated2());
    for (Object object : objects) {
        // What is the simplest way to call `doCommon` with object here?
        doCommon(adapt(object));
    }
}

private CommonInterface adapt(Object o) {
    return adapt(o, CommonInterface.class);
}

public static <T> T adapt(final Object adaptee,
                          final Class<T>... interfaceToImplement) {
    return (T) Proxy.newProxyInstance(
        adaptee.getClass().getClassLoader(),
        interfaceToImplement,
        // Call the equivalent method from the adaptee.
        (proxy, method, args) -> adaptee.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(adaptee, args));
}

Ответы [ 4 ]

2 голосов
/ 23 апреля 2019

Если вы используете отражение, вам не нужны два класса CommonInterfaceX, вы можете использовать прокси, реализующий CommonInterface:

public class Wrapper implements InvocationHandler {
    private final Object delegate;

    public static <T> T wrap(Object obj, Class<T> intf) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Object proxy = Proxy.newProxyInstance(cl, new Class<?>[] {intf},
                        new Wrapper(obj));
        return intf.cast(proxy);
    }

    private Wrapper(Object delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Method dmethod = delegate.getClass().getMethod(
                method.getName(), method.getParameterTypes());
        return dmethod.invoke(delegate, args);
    }
}

Вы можете использовать этот класс следующим образом:

List<Object> objects = Arrays.asList(new Generated1(), new Generated2());
for (Object object : objects) {
    CommonInterface proxy = Wrapper.wrap(object, CommonInterface.class);
    doCommon(proxy);
}

ОБНОВЛЕНИЕ: обратите внимание, что тот же класс Wrapper работает с любым интерфейсом.

0 голосов
/ 23 апреля 2019

Вы можете сделать это вручную с помощью отражения.

public class Generated {
    public String getA() {
        return "A";
    }

    public String sayHello(String name) {
        return "hello " + name;
    }
}

public class Helper {
    private static final String METHOD_NAME = "getA";
    private static final String METHOD_WITH_PARAM_NAME = "sayHello";

    public static void main(String[] args) throws Exception {
        Generated generated = new Generated();

        accessMethod(generated);
        accessMethodWithParameter(generated);
    }

    private static void accessMethod(Generated g) throws Exception {
        Method[] methods = g.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(isCommonMethod(method)) {
                String result = (String) method.invoke(g);
                System.out.println(METHOD_NAME + "() = " + result);
            }
        }
    }

    private static boolean isCommonMethod(Method m) {
        return m.getName().equals(METHOD_NAME) && m.getReturnType().equals(String.class);
    }

    private static void accessMethodWithParameter(Generated g) throws Exception {
        Method[] methods = g.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(isCommonMethodWithParameter(method)) {
                String result = (String) method.invoke(g, "Max");
                System.out.println(METHOD_WITH_PARAM_NAME + "(\"Max\") = " + result);
            }
        }
    }

    private static boolean isCommonMethodWithParameter(Method m) {
        return m.getName().equals(METHOD_WITH_PARAM_NAME) &&
                m.getReturnType().equals(String.class) &&
                m.getParameterTypes().length == 1 &&
                m.getParameterTypes()[0].equals(String.class);
    }

}

Выход

getA() = A
sayHello("Max") = hello Max
0 голосов
/ 23 апреля 2019

Если вы хотите заменить в качестве комментария.Я думаю, что вы можете сделать это легко

Сначала вы создаете интерфейс CommonInterface

interface CommonInterface {
    String getA();

    void setB(String b);

    void setC(String c);

    void setD(String d);
}

После этого вы создаете 2 класса Generated1 и Generated2 наследуемые CommonInterface

class Generated1 implements CommonInterface {
    @overide
    public String getA() {
        return "1";
    }

    @overide
    public void setB(String b) {
    }

    @overide
    public void setC(String c) {
    }

    @overide
    public void setD(String d) {
    }
}

class Generated2 implements CommonInterface {

    @overide
    public String getA() {
        return "2";
    }

    @overide
    public void setB(String b) {
    }

    @overide
    public void setC(String c) {
    }

    @overide
    public void setD(String d) {
    }
}
0 голосов
/ 23 апреля 2019

Нет никакого способа добиться статического типа связи между Generated1 и Generated2.

Даже если вы создали CommonInterface1 и CommonInterface2, вы все равно не сможете статически использоватьGenerated1 объект как CommonInterface1, потому что new Generated1() не является a CommonInterface1 (и никогда не станет единым целым)

На сегодняшний день самым простым решением являетсяизменить генерацию кода, добавив CommonInterface к Generated1 и Generated2.

Если это абсолютно невозможно, единственный другой способ избежать этого дублирования кода - это подумать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...