我有一个来自另一个闭源的库,但我希望能够使用它的接口。原因是我不想进行instanceof
检查或null
- 检查所有地方,但我也不想扩展现有的类。
例如,假设我有这段代码:
public class Example {
// QuietFoo is from another library that I can't change
private static QuietFoo quietFoo;
// LoudFoo is my own code and is meant to replace QuietFoo
private static LoudFoo loudFoo;
public static void main(String[] args) {
handle(foo);
}
private static void handle(Object foo) {
if (foo instanceof QuietFoo)
((QuietFoo) foo).bar();
else if (foo instanceof LoudFoo)
((LoudFoo) foo).bar();
}
}
我无法更改QuietFoo
:
public class QuietFoo {
public void bar() {
System.out.println("bar");
}
}
但我可以更改LoudFoo
:
public class LoudFoo {
public void bar() {
System.out.println("BAR!!");
}
}
问题是,在许多类中可能还有许多其他bar
的实现,并且可能有更多的方法而不仅仅是bar
,因此我的handle
方法不仅会变慢并且有很多instanceof
语句很难看,但我必须为QuietFoo
和LoudFoo
上的每个方法编写其中一种句柄方法。扩展不是一个可行的解决方案,因为它违反了整个 is-a 合同,因为LoudFoo
不是QuietFoo
。
基本上,给定Foo
:
public interface Foo {
void bar();
}
如何QuietFoo
实施Foo
而不更改其来源,以便我不必在代码中的任何地方进行投射和instanceof
调用?
答案 0 :(得分:14)
有两种方法:
Proxy
适配器方法将更简单但灵活性更低,Proxy
方法将更复杂但更灵活。尽管Proxy
方法更复杂,但这种复杂性仅限于几个类。
<强>适配器强>
adapter pattern很简单。对于你的例子,它只是一个类,如下:
public class QuietFooAdapter implements Foo {
private QuietFoo quietFoo;
public QuietFooAdapter(QuietFoo quietFoo) {
this.quietFoo = quietFoo;
}
public void bar() {
quietFoo.bar();
}
}
然后使用它:
Foo foo = new QuietFooAdapter(new QuietFoo());
foo.bar();
这很好,但是如果你有一个以上的类来制作适配器,这可能很乏味,因为你需要为每个必须包装的类提供一个新的适配器。
Java的Proxy
班级
Proxy
是一个本机Java类,它是反射库的一部分,可以让您创建更通用的反射解决方案。它涉及3个部分:
Foo
)InvocationHandler
Proxy.newProxyInstance
)我们已经有了界面,所以我们很好。
InvocationHandler
是我们自行调整的地方&#34;自动适应&#34;通过反思:
public class AdapterInvocationHandler implements InvocationHandler {
private Object target;
private Class<?> targetClass;
public AdapterInvocationHandler(Object target) {
this.target = target;
targetClass = target.getClass();
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Method targetMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
if (!method.getReturnType().isAssignableFrom(targetMethod.getReturnType()))
throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not support: " + method.toGenericString());
return targetMethod.invoke(target, args);
} catch (NoSuchMethodException ex) {
throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not support: " + method.toGenericString());
} catch (IllegalAccessException ex) {
throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not declare method to be public: " + method.toGenericString());
} catch (InvocationTargetException ex) {
// May throw a NullPointerException if there is no target exception
throw ex.getTargetException();
}
}
}
此处的重要代码位于try
块中。这将处理将代理上调用的任何方法调用适配到内部target
对象的过程。如果在不支持的接口上调用方法(非public
,错误的返回类型,或者只是平坦不存在),那么我们抛出UnsupportedOperationException
。如果我们抓住InvocationTargetException
,我们会重新抛出通过InvocationTargetException.getTargetException
导致它的异常。当我们调用的方法反射抛出异常时会发生这种情况。 Java将其包装在一个新的异常中并抛出该新异常。
接下来,我们需要一些东西来创建适配器:
public class AdapterFactory {
public static <T> T createAdapter(Object target, Class<T> interfaceClass) {
if (!interfaceClass.isInterface())
throw new IllegalArgumentException("Must be an interface: " + interfaceClass.getName());
return (T) Proxy.newProxyInstance(null, new Class<?>[] { interfaceClass }, new AdapterInvocationHandler(target));
}
}
如果您愿意,也可以将AdapterInvocationHandler
课程嵌套在AdapterFactory
课程中,以便AdapterFactory
中所有内容都自包含。
然后使用它:
Foo foo = AdapterFactory.createAdapter(new QuietFoo(), Foo.class);
foo.bar();
这种方法比实现单个适配器需要更多代码,但是它足够通用,可以用于为任何类和接口对创建自动适配器,而不仅仅是QuietFoo
和Foo
例。当然,这种方法使用反射(Proxy
类使用反射,我们的InvocationHandler
也是如此),可以更慢,但JVM的最近改进使反射更快比过去好。