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

时间:2012-11-13 20:33:43

标签: 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 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语句很难看,但我必须为QuietFooLoudFoo上的每个方法编写其中一种句柄方法。扩展不是一个可行的解决方案,因为它违反了整个 is-a 合同,因为LoudFoo不是QuietFoo

基本上,给定Foo

public interface Foo {
    void bar();
}

如何QuietFoo实施Foo而不更改其来源,以便我不必在代码中的任何地方进行投射和instanceof调用?

1 个答案:

答案 0 :(得分:14)

有两种方法:

  1. 使用适配器模式
  2. 使用Proxy
  3. 适配器方法将更简单但灵活性更低,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类,它是反射库的一部分,可以让您创建更通用的反射解决方案。它涉及3个部分:

    1. 界面(在本例中为Foo
    2. InvocationHandler
    3. 创建代理(Proxy.newProxyInstance
    4. 我们已经有了界面,所以我们很好。

      InvocationHandler是我们自行调整的地方&#34;自动适应&#34;通过反思:

      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的最近改进使反射更快比过去好。