Java 如何创建一个我可以';不改变,实现一个接口?

Java 如何创建一个我可以';不改变,实现一个接口?,java,interface,adapter,instanceof,proxy-classes,Java,Interface,Adapter,Instanceof,Proxy Classes,我有一个来自另一个封闭源代码库的类,但我希望能够为它使用接口。原因是我不想执行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 mean

我有一个来自另一个封闭源代码库的类,但我希望能够为它使用接口。原因是我不想执行
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");
    }
}
public class LoudFoo {

    public void bar() {
        System.out.println("BAR!!");
    }
}
public interface Foo {
    void bar();
}
但是我可以改变LoudFoo

public class QuietFoo {

    public void bar() {
        System.out.println("bar");
    }
}
public class LoudFoo {

    public void bar() {
        System.out.println("BAR!!");
    }
}
public interface Foo {
    void bar();
}
问题是,在许多类中可能有许多其他的
bar
实现,并且可能有比
bar
更多的方法,因此我的
处理
方法不仅会变得缓慢和丑陋,而且会有很多
语句的实例,但是我必须为
QuietFoo
LoudFoo
上的每个方法编写一个句柄方法。扩展不是一个可行的解决方案,因为它违反了整个is-a契约,因为
LoudFoo
不是
QuietFoo

基本上,给定
Foo

public class QuietFoo {

    public void bar() {
        System.out.println("bar");
    }
}
public class LoudFoo {

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

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

有两种方法:

  • 使用适配器模式
  • 使用
  • 适配器方法将更简单,但灵活性较差,
    代理方法将更复杂,但更灵活。尽管代理方法更为复杂,但这种复杂性仅限于几个类


    适配器

    问题很简单。例如,它将只是一个类,如下所示:

    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();
    
    Foo foo = AdapterFactory.createAdapter(new QuietFoo(), Foo.class);
    foo.bar();
    
    这很好,但是如果要为多个类创建适配器,这可能会很乏味,因为您需要为每个必须包装的类创建一个新的适配器


    Java的
    Proxy
    Class

    Proxy
    是一个本机Java类,它是反射库的一部分,允许您创建更通用的反射解决方案。它包括三个部分:

  • 接口(在本例中,
    Foo
  • 创建代理()
  • 我们已经有了接口,所以我们在那里很好

    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();
            }
        }
    }
    
    如果愿意,您还可以将
    adapterHolidationHandler
    类嵌套在
    AdapterFactory
    类中,以便所有内容都包含在
    AdapterFactory

    然后使用它:

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

    这种方法比实现单个适配器需要更多的代码,但它具有足够的通用性,可以用于为任何类和接口对创建自动适配器,而不仅仅是
    QuietFoo
    Foo
    示例。诚然,这种方法使用反射(代理类使用反射,我们的调用处理程序也使用反射),这可能会慢一些,但JVM最近的改进使反射比以前快得多。

    在有人批评我之前:@Gamb阅读我评论中的那篇博文--如果你把鼠标移到“x分钟前”,你会看到,从技术上讲,时间是完全相同的:3Ah,好吧。当你发布一个问题时,你可以选择“回答你自己的问题”(底部的复选框),这也会给你一个输入答案的位置。这就是我如何让它在同一时间提交的。我使用了“你”“这样人们就可以用对话的方式来看待它,让它更容易阅读。我知道这一点。它将其转换为wiki条目,而不是问题答案(IMHO)。不用担心,这是一个很酷的答案,但我的评论似乎属于meta的问题,而不是这里:)@Gamb如果你问一个关于meta的问题,让我知道你发现了什么。+1有趣的反射方法(这里是反射爱好者)。