用相同的方法但不实现相同的接口为一组类编写通用代码的惯用方式是什么?

时间:2019-05-06 06:57:15

标签: java inheritance interface

我正在使用一个外部库,该库提供紧密相关的类(从某些模板生成),但是不幸的是,没有共享的接口,例如

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());
    

    但这可能会变得很罗word,尤其是当我需要与许多成员一起工作时。

  2. 在仅使用吸气剂的情况下( ie 仅需要访问对象的当前值),我找到了一种使用映射库(如Dozer或ModelMapper将A或B或C映射到我自己的通用类 eg

    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 个答案:

答案 0 :(得分:3)

唯一未尝试的技术:

package aplus;

public interface Common {
    ...
}

public class A extends original.A implements Common {
}

public class B extends original.B implements Common {
}

答案 1 :(得分:2)

(实际上)类似于第二种方法,但相对精简和灵活的一种选择是使用Dynamic Proxy Classes。仅需几行代码,只要具有所需的方法,就可以让任何“对象”“实现”以实现某个接口。以下是显示基本方法的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的位置添加一些明智的错误检查:在此,接口的方法在给定的委托对象中查找。