8

我有一个来自另一个闭源库的类,但我希望能够为它使用接口。原因是我不想到处做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不仅仅是对于和上的每种方法。扩展不是一个可行的解决方案,因为它违反了整个is-a合同,因为is not a .barhandleinstanceofQuietFooLoudFooLoudFooQuietFoo

基本上,给定Foo

public interface Foo {
    void bar();
}

如何在不更改其源代码的情况下QuietFoo实现实现Foo,这样我就不必instanceof在代码中的任何地方进行强制转换和调用?

4

1 回答 1

15

有两种方法:

  1. 使用适配器模式
  2. 使用Proxy

适配器方法将更简单但不太灵活,而该Proxy方法将更复杂但更灵活。尽管该Proxy方法更复杂,但这种复杂性都仅限于几个类。


适配器

适配器模式很简单。对于您的示例,它只是一个类,如下所示:

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个部分:

  1. 接口(在本例中为Foo
  2. InvocationHandler
  3. 创建代理 ( Proxy.newProxyInstance)

我们已经有了接口,所以我们很好。

InvocationHandler是我们通过反射进行“自动适应”的地方:

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

这种方法比实现单个适配器需要更多的代码,但足够通用,可以用于为任何类和接口对创建自动适配器,而不仅仅是QuietFooFoo示例。诚然,这种方法使用反射(Proxy类使用反射,我们的 也是如此InvocationHandler),这可能会更慢,但最近 JVM 的改进使反射比以前快得多。

于 2012-11-13T20:33:42.760 回答