如何让一个我无法更改的类实现一个接口?

2024-03-27

我有一个来自另一个库的闭源类,但我希望能够使用它的接口。原因是我不想做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");
    }
}

But I can change LoudFoo:

public class LoudFoo {

    public void bar() {
        System.out.println("BAR!!");
    }
}

问题是,可能还有许多其他实现bar在许多类中,可能有更多的方法bar,所以我不仅会handle方法变得缓慢且丑陋,有很多instanceof语句,但我必须为每个方法编写这些句柄方法之一QuietFoo and LoudFoo。扩展不是一个可行的解决方案,因为它违反了整体is-a合同自LoudFoo不是一个QuietFoo.

基本上,给定Foo:

public interface Foo {
    void bar();
}

我怎样才能使QuietFoo实施Foo不改变它的来源,所以我不必进行铸造和instanceof在我的代码中到处调用?


有两种方法:

  1. 使用适配器模式
  2. Using Proxy http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html

适配器方法会更简单但灵活性较差,并且Proxy方法将更加复杂但更加灵活。尽管Proxy方法更复杂,这种复杂性仅限于几个类。


Adapter

The 适配器模式 http://www.oodesign.com/adapter-pattern.html很简单。对于您的示例,它只是一个类,如下所示:

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's Proxy Class

Proxy是一个本机 Java 类,它是反射库的一部分,可让您创建更通用的反射解决方案。它涉及3个部分:

  1. 接口(在本例中,Foo)
  2. The InvocationHandler http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/InvocationHandler.html
  3. 创建代理(Proxy.newProxyInstance http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html#newProxyInstance%28java.lang.ClassLoader,%20java.lang.Class%5B%5D,%20java.lang.reflect.InvocationHandler%29)

我们已经有了界面,所以一切都很好。

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何让一个我无法更改的类实现一个接口? 的相关文章

随机推荐