我正在使用一个外部库,该库提供紧密相关的类(从某些模板生成),但是不幸的是,没有共享的接口,例如
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.
鉴于我对外部库没有任何影响,对于共享相同方法签名的一组类(至少对于公共逻辑所使用的方法而言)编写公共逻辑的惯用方式是什么?
当前,我会根据具体情况执行以下三项操作之一:
我编写了辅助方法,这些方法从每个对象(例如
)获取原始结果private static void myHelper(UUID id, Long version, String foo, String bar) {
...
}
通过这种方式,我可以“解压缩”一个对象,而不管它的类型是什么:
myHelper(whatever.id(), whatever.version(), whatever.foo(), whatever.bar());
但这可能会变得很罗word,尤其是当我需要与许多成员一起工作时。
在仅使用吸气剂的情况下( 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显然没有理由仅仅为了排除重复的代码而造成的黑客攻击。
最后,在其他某些情况下,简单地做到这一点最简洁
private static void myHelper(Object extLibEntity) {
if (extLibEntity instanceof A) {
...
} else if (extLibEntity instanceof B) {
...
} else if (extLibEntity instanceof C) {
...
} else {
throw new RuntimeException(...);
}
}
很明显,为什么这很糟糕,我们不要谈论它。
在企业环境中,您必须以这种方式来使用图书馆,您将怎么办?
我倾向于编写一个非常明确的(如果是冗长的)映射器(不使用通用映射器库),该映射器将从一开始就翻译这些实体。但是,我想知道是否有更好的方法。 (例如,是否有一种方法可以在运行时将对象“投射”为实现新接口?)
答案 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
的位置添加一些明智的错误检查:在此,接口的方法在给定的委托对象中查找。