Каков идиоматический способ написания общего кода для группы классов с одинаковыми методами, но без реализации одного и того же интерфейса? - PullRequest
5 голосов
/ 06 мая 2019

Я использую внешнюю библиотеку, которая предоставляет тесно связанные классы (сгенерированные из некоторого шаблона), но, к сожалению, без общего интерфейса, например,

public class A {
    public UUID id();
    public Long version();
    public String foo();
    public String bar();
}

public class B {
    public UUID id();
    public Long version();
    public String foo();
    public String bar();
}

public class C {
    public UUID id();
    public Long version();
    public String foo();
    public String bar();
}

// ... and more: D, E, F, etc.

Учитывая, что я не имею никакого влияния на внешнюю библиотеку, каков идиоматический способ написания логики, общей для группы классов, имеющих общие сигнатуры методов (по крайней мере, для методов, используемых общей логикой)?

В настоящее время я делаю один изтри вещи, в каждом конкретном случае:

  1. Я пишу вспомогательные методы, которые получают примитивные результаты от каждого объекта, например,

    private static void myHelper(UUID id, Long version, String foo, String bar) {
      ...
    }
    

    Таким образом, я могу "распаковать" объект независимо от его типа:

    myHelper(whatever.id(), whatever.version(), whatever.foo(), whatever.bar());
    

    Но это может стать очень многословным, особенно когда мне нужно работать со многими членами.

  2. В сценарии, где я работаю только с геттерами ( т.е. требуется только доступ к текущим значениям объектов), я нашел способ использовать библиотеки отображения, такие как Dozer или ModelMapper, длясопоставить A или B или C смой собственный общий класс, например

    public class CommonABC {
      UUID id;
      Long version;
      String foo;
      String bar;
    }
    

    Играя с конфигурацией, вы можете получить эти библиотеки для отображения всех членов, будь то метод или поле, открытый или закрытый, для вашего класса, например

    modelMapper.getConfiguration()
        .setFieldMatchingEnabled(true)
        .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);
    

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

  3. Наконец, в некоторых других сценариях было наиболее кратким просто сделать

    private static void myHelper(Object extLibEntity) {
      if (extLibEntity instanceof A) {
        ...
      } else if (extLibEntity instanceof B) {
        ...
      } else if (extLibEntity instanceof C) {
        ...
      } else {
        throw new RuntimeException(...);
      }
    }
    

    Очевидно, почему это плохо.

В корпоративных ситуациях, когда выЯ должен жить с библиотекой, которая именно таким образом, что бы вы сделали?

Я склоняюсь к написанию очень явного, если многословно, маппера (не использующего универсальную библиотеку мапперов), который переводит эти сущности изНачните.Но мне интересно, есть ли лучший способ.(Например, есть ли способ «привести» объект к реализации нового интерфейса во время выполнения?)

Ответы [ 2 ]

3 голосов
/ 06 мая 2019

Единственная не испытанная техника:

package aplus;

public interface Common {
    ...
}

public class A extends original.A implements Common {
}

public class B extends original.B implements Common {
}
2 голосов
/ 06 мая 2019

Опция, которая (под капотом), вероятно, аналогична второму подходу, но сравнительно компактна и гибка, - это использовать Динамические прокси-классы . Имея всего несколько строк кода, вы можете позволить любому объекту «появляться» для реализации определенного интерфейса, если у него есть необходимые методы. Ниже приводится MCVE, который показывает базовый подход:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;

public class DelegatingProxyExample {

    public static void main(String[] args) {

        A a = new A();
        B b = new B();
        C c = new C();

        CommonInterface commonA = wrap(a);
        CommonInterface commonB = wrap(b);
        CommonInterface commonC = wrap(c);

        use(commonA);
        use(commonB);
        use(commonC);
    }

    private static void use(CommonInterface commonInterface) {
        System.out.println(commonInterface.id());
        System.out.println(commonInterface.version());
        System.out.println(commonInterface.foo());
        System.out.println(commonInterface.bar());
    }

    private static CommonInterface wrap(Object object) {
        CommonInterface commonInterface = (CommonInterface) Proxy.newProxyInstance(
            CommonInterface.class.getClassLoader(), 
            new Class[] { CommonInterface.class }, new Delegator(object));
        return commonInterface;
    }

}

// Partially based on the example from
// https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
class Delegator implements InvocationHandler {

    private static Method hashCodeMethod;
    private static Method equalsMethod;
    private static Method toStringMethod;
    static {
        try {
            hashCodeMethod = Object.class.getMethod("hashCode", (Class<?>[]) null);
            equalsMethod = Object.class.getMethod("equals", new Class[] { Object.class });
            toStringMethod = Object.class.getMethod("toString", (Class<?>[]) null);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    private Object delegate;

    public Delegator(Object delegate) {
        this.delegate = delegate;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        Class<?> declaringClass = m.getDeclaringClass();

        if (declaringClass == Object.class) {
            if (m.equals(hashCodeMethod)) {
                return proxyHashCode(proxy);
            } else if (m.equals(equalsMethod)) {
                return proxyEquals(proxy, args[0]);
            } else if (m.equals(toStringMethod)) {
                return proxyToString(proxy);
            } else {
                throw new InternalError("unexpected Object method dispatched: " + m);
            }
        } else {

            // TODO Here, the magic happens. Add some sensible error checks here!
            Method delegateMethod = delegate.getClass().getDeclaredMethod(
                m.getName(), m.getParameterTypes());
            return delegateMethod.invoke(delegate, args);
        }
    }

    protected Integer proxyHashCode(Object proxy) {
        return new Integer(System.identityHashCode(proxy));
    }

    protected Boolean proxyEquals(Object proxy, Object other) {
        return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
    }

    protected String proxyToString(Object proxy) {
        return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
    }
}

interface CommonInterface {
    UUID id();

    Long version();

    String foo();

    String bar();
}

class A {
    public UUID id() {
        return UUID.randomUUID();
    }

    public Long version() {
        return 1L;
    }

    public String foo() {
        return "fooA";
    }

    public String bar() {
        return "barA";
    }
}

class B {
    public UUID id() {
        return UUID.randomUUID();
    }

    public Long version() {
        return 2L;
    }

    public String foo() {
        return "fooB";
    }

    public String bar() {
        return "barB";
    }
}

class C {
    public UUID id() {
        return UUID.randomUUID();
    }

    public Long version() {
        return 3L;
    }

    public String foo() {
        return "fooC";
    }

    public String bar() {
        return "barC";
    }
}

Конечно, для этого используется внутреннее отражение, и его следует использовать только тогда, когда вы знаете, что делаете. В частности, вы должны добавить некоторую разумную проверку ошибок в месте, помеченном TODO: там, метод интерфейса ищется в данном объекте делегата.

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