如何创建一个我无法更改的类,实现一个接口? [英] How do I make a class, which I can't change, implement an interface?

查看:175
本文介绍了如何创建一个我无法更改的类,实现一个接口?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a class from another library that is closed-source, but I want to be able to use an interface for it. The reason being that I don't want to do instanceof checks or null-checks everywhere, but I also don't want to extend the existing class.

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

For example, let's say I have this code:

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

The problem is, there may be many other implementations of bar in many classes, and there may be more methods than just bar, so not only would my handle method get slow and ugly with lots of instanceof statements, but I would have to write one of these handle methods for each method on QuietFoo and LoudFoo. Extending isn't a viable solution because it violates the whole is-a contract since LoudFoo is not a QuietFoo.

基本上,给定 Foo

public interface Foo {
    void bar();
}

如何制作 QuietFoo 实现 Foo 而不更改其源代码,因此我无需在代码中的任何地方进行转换和 instanceof 调用?

How can I make QuietFoo implement Foo without changing its source so I don't have to do casting and instanceof calls everywhere in my code?

推荐答案

有两种方法:


  1. 使用适配器模式

  2. 使用 代理商

适配器方法将更简单但更不灵活,代理方法将更复杂但更灵活。即使代理方法更复杂,但这种复杂性仅限于几个类。

The adapter approach will be simpler but less flexible, and the Proxy approach will be more complex but more flexible. Even though the Proxy approach is more complex, that complexity is all restricted to a couple of classes.

适配器

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

The adapter pattern is simple. For your example, it would just be one class, like so:

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

这很好,但如果你有多个类来制作适配器,这可以是繁琐,因为你需要为你必须包装的每个类都需要一个新的适配器。

This is good, but if you have more than one class to make an adapter for, this can be tedious since you'll need a new adapter for each class you have to wrap.

Java的代理

Java's Proxy Class

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

Proxy is a native Java class that's part of the reflection libraries that will allow you to create a more generic, reflective solution. It involves 3 parts:


  1. 接口(在这种情况下, Foo

  2. InvocationHandler

  3. 创建代理( Proxy.newProxyInstance

  1. The interface (in this case, Foo)
  2. The InvocationHandler
  3. Creation of the proxy (Proxy.newProxyInstance)

我们已经拥有界面,所以我们在那里很好。

We already have the interface, so we're fine there.

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

The InvocationHandler is where we do our "auto-adapting" via reflection:

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

这里的重要代码是尝试块。这将处理将代理上调用的任何方法调用适应内部目标对象的过程。如果在不受支持的接口上调用了一个方法(非 public ,错误的返回类型,或者只是扁平化不存在),那么我们抛出一个 UnsupportedOperationException异常。如果我们捕获 InvocationTargetException ,我们将通过 InvocationTargetException.getTargetException 重新抛出导致它的异常。当我们调用的方法反射抛出异常时会发生这种情况。 Java将它包装在一个新的异常中并抛出新的异常。

The important code here is in the try block. This will handle the process of adapting any method calls that are called on the proxy to the inner target object. If a method is called on the interface that isn't supported (non-public, wrong return type, or just flat out doesn't exist), then we throw an UnsupportedOperationException. If we catch an InvocationTargetException, we rethrow the exception that caused it via InvocationTargetException.getTargetException. This occurs when the method we called reflectively throws an exception. Java wraps it in a new exception and throws that new exception.

接下来,我们需要一些东西来创建适配器:

Next, we need something to create the adapters:

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

您还可以嵌套如果你愿意,可以在 AdapterFactory 类中使用AdapterInvocationHandler 类,以便所有内容都自包含在 AdapterFactory

You could also nest the AdapterInvocationHandler class in the AdapterFactory class, if you like, so that everything is self-contained in AdapterFactory.

然后使用它:

Foo foo = AdapterFactory.createAdapter(new QuietFoo(), Foo.class);
foo.bar();

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

This approach requires more code than implementing a single adapter, but will be generic enough that it can be used to create auto-adapters for any class and interface pair, not just the QuietFoo and Foo example. Granted, this method uses reflection (the Proxy class uses reflection, as does our InvocationHandler), which can be slower, but recent improvements in the JVM have made reflection much faster than it used to be.

这篇关于如何创建一个我无法更改的类,实现一个接口?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆