【问题标题】:How do I make a class, which I can't change, implement an interface?如何让一个我无法更改的类实现一个接口?
【发布时间】:2012-11-13 20:33:42
【问题描述】:

我有一个来自另一个闭源库的类,但我希望能够为它使用接口。原因是我不想到处做instanceof 检查或null-checks,但我也不想扩展现有的类。

例如,假设我有这个代码:

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 方法会因为有很多@ 而变得缓慢而丑陋987654333@ 语句,但我必须为QuietFooLoudFoo 上的每个方法编写这些句柄方法之一。扩展不是一个可行的解决方案,因为它违反了整个 is-a 合同,因为 LoudFoo 不是 QuietFoo

基本上,给定Foo

public interface Foo {
    void bar();
}

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

【问题讨论】:

  • 在任何人给我高炮之前:blog.stackoverflow.com/2011/07/…
  • @Gamb 在我的评论中阅读该博客文章 -.- 如果您将鼠标悬停在“x 分钟前”上,从技术上讲,您会看到完全相同的时间:3
  • 啊,好吧。当您发布问题时,您可以选择“回答您自己的问题”(this page 底部的复选框),这也可以让您输入答案。这就是我让它在同一时间提交的方式。我使用“你”是为了让人们以对话的方式查看它,使其更易于阅读。
  • 我知道。它把它变成一个维基条目而不是一个问答(恕我直言)。不用担心,这是一个很酷的答案,但我的评论似乎属于元问题而不是这里:)
  • @Gamb 如果您对 meta 提出问题,请告诉我您的发现。

标签: java interface adapter instanceof proxy-classes


【解决方案1】:

有两种方法:

  1. 使用适配器模式
  2. 使用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 类,它是反射库的一部分,可让您创建更通用的反射解决方案。它涉及三个部分:

  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 的改进使反射比以前快得多是。

【讨论】:

  • +1 有趣的反射方法(反射爱好者在这里)。
猜你喜欢
  • 2015-03-21
  • 2016-08-31
  • 1970-01-01
  • 2012-01-09
  • 1970-01-01
  • 2014-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多