java – 如何创建一个我无法更改的类,实现一个接口?

我有一个来自另一个闭源的库的类,但我希望能够使用它的接口.原因是我不想在任何地方进行instanceof检查或空检查,但我也不想扩展现有的类.

例如,假设我有这样的代码

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上每个方法方法.扩展不是一个可行的解决方案,因为它违反了整个合同,因为LoudFoo不是QuietFoo.

基本上,给予Foo:

public interface Foo {
    void bar();
}

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

解决方法

有两种方法

>使用适配器模式
>使用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是一个本机java类,它是反射库的一部分,允许您创建更通用的反射解决方案.它涉及3个部分:

>界面(在这种情况下,Foo)
> InvocationHandler
>创建代理(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块中.这将处理将代理上调用的任何方法调用适配到内部目标对象的过程.如果在不支持的接口上调用一个方法(非公共,错误的返回类型,或者只是不存在),那么我们抛出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的最近改进使得反射比以前快得多.

相关文章

最近看了一下学习资料,感觉进制转换其实还是挺有意思的,尤...
/*HashSet 基本操作 * --set:元素是无序的,存入和取出顺序不...
/*list 基本操作 * * List a=new List(); * 增 * a.add(inde...
/* * 内部类 * */ 1 class OutClass{ 2 //定义外部类的成员变...
集合的操作Iterator、Collection、Set和HashSet关系Iterator...
接口中常量的修饰关键字:public,static,final(常量)函数...