2013-04-08 79 views
5

我有很多使用setListener方法注册的监听器,而不是addListener。所以为了允许多个监听器注册一个对象,我必须使用多路复用器。这很好,但现在我必须为每个侦听器接口创建一个多路复用器。所以我的问题是:是否可以按照以下代码的要求实现Mux.create()?是否可以在Java中编写通用多路复用器?

AppleListener appleListener1 = new AppleProcessorA(); 
AppleListener appleListener2 = new AppleProcessorB(); 
AppleListener appleListenerMux = Mux.create(appleListener1, appleListener2); 
Apple apple = new Apple(); 
apple.setListener(appleListenerMux); 

OrangeListener orangeListener1 = new OrangeProcessorA(); 
OrangeListener orangeListener2 = new OrangeProcessorB(); 
OrangeListener orangeListenerMux = Mux.create(orangeListener1, orangeListener2); 
Orange apple = new Orange(); 
orange.setListener(orangeListenerMux); 

class Mux { 
    public static <T> T create(T... outputs) { } 
} 

我想这可能是使用反射。有没有什么理由使用反射会是一个坏主意? (性能出现在脑海里)

+0

如果所有侦听器都实现'AppleListener'接口,我不认为需要a)反射,也不b)泛型。只需把它们全部添加到你的''Mux''的'List '中并重复。或者我错过了什么? – 2013-04-08 05:48:16

+0

您能解释一下关于您的多路复用器的更多信息吗?因为同样的事情出现在我的脑海里,正如阿利斯泰尔以色列所说的那样。 – 2013-04-08 05:49:39

+2

尽管它稍微超出了预期的范围,但可以使用[Proxy](http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html)类。这创建了一个Object,它看起来像给出的类,但调用一个处理程序来处理它的调用,然后处理程序可以遍历这些侦听器。 – BevynQ 2013-04-08 06:13:39

回答

9

这是可能的使用动态Proxy

最简单的方法是将所需的界面作为第一个参数传递给您的Mux.create()调用。否则,您将不得不使用反射来从所提供的所有具体侦听器实例中猜测所需的接口(难以确定所有侦听器对象是否实现了几个共同的接口)。

下面是它的短:

public class Mux { 

    /** 
    * @param targetInterface 
    *   the interface to create a proxy for 
    * @param instances 
    *   the concrete instances to delegate to 
    * @return a proxy that'll delegate to all the arguments 
    */ 
    @SuppressWarnings("unchecked") 
    public static <T> T create(Class<T> targetInterface, final T... instances) { 
     ClassLoader classLoader = targetInterface.getClassLoader(); 
     InvocationHandler handler = new InvocationHandler() { 
      @Override 
      public Object invoke(Object proxy, Method m, Object[] args) 
        throws Throwable { 
       for (T instance : instances) { 
        m.invoke(instance, args); 
       } 
       return null; 
      } 
     }; 
     return (T) Proxy.newProxyInstance(classLoader, 
       new Class<?>[] { targetInterface }, handler); 
    } 
} 

,你会使用,例如,如下:

Apple apple = new Apple(); 
AppleListener l1 = new AppleListenerA(); 
AppleListener l2 = new AppleListenerB(); 
apple.setListener(Mux.create(AppleListener.class, l1, l2)); 
apple.doSomething(); // will notify all listeners 

这是通过简单地创建一个动态Proxy被强制转换为目标类型T。该代理使用InvocationHandler,该代理只将代理的所有方法调用委托给给定的具体实例。

注意,尽管在一般我完成所有的参数和局部变量只要有可能,我只有在这种情况下完成T... instances强调这一事实,如果instances决赛,然后引用它的匿名内部类中不会被允许(你会得到一个“不能引用在不同方法中定义的内部类中的非最终变量参数”)。

还要注意的是,上述假设的实际方法调用不返回任何有意义的(或有用的)值,因此handler也返回null所有方法调用。如果您想收集返回值并以有意义的方式返回这些值,则需要添加更多代码。


另外,可以检查所有给instances以确定它们都实现了常用接口,并通过所有那些newProxyInstance()。这使得Mux.create()使用起来更加方便,并且失去了对其行为的一些控制。

/** 
* @param instances 
*   the arguments 
* @return a proxy that'll delegate to all the arguments 
*/ 
@SuppressWarnings("unchecked") 
public static <T> T create(final T... instances) { 

    // Inspect common interfaces 
    final Set<Class<?>> commonInterfaces = new HashSet<Class<?>>(); 
    commonInterfaces.addAll(Arrays.asList(instances[0].getClass() 
      .getInterfaces())); 

    // Or skip instances[0] 
    for (final T instance : instances) { 
     commonInterfaces.retainAll(Arrays.asList(instance.getClass() 
       .getInterfaces())); 
    } 

    // Or use ClassLoader.getSystemClassLoader(); 
    final ClassLoader classLoader = instances[0].getClass().getClassLoader(); 

    // magic 
    final InvocationHandler handler = new InvocationHandler() { 
     @Override 
     public Object invoke(final Object proxy, final Method m, final Object[] args) 
       throws Throwable { 
      for (final T instance : instances) { 
       m.invoke(instance, args); 
      } 
      return null; 
     } 
    }; 

    final Class<?>[] targetInterfaces = commonInterfaces 
      .toArray(new Class<?>[commonInterfaces.size()]); 
    return (T) Proxy.newProxyInstance(classLoader, targetInterfaces, 
      handler); 
} 
+0

谢谢,我感谢你的答案的彻底性。出于好奇的一个问题:我们能否从泛型参数'T'推断出接口,而不是将它作为另一个参数传递? – 2013-04-09 00:17:53

+0

@DylanP你不能从泛型参数'T'中推断任何东西,因为它不是'实现'的 - 也就是说,该参数在运行时并不真正可用。但是,有些边缘案例或技巧可以在运行时推断泛型类型信息:[子类](http://alistairisrael.wordpress.com/2009/05/28/introducing-magictest/) ,并使用[类型标记](http://www.jquantlib.org/index.php/Using_TypeTokens_to_retrieve_generic_parameters)。 – 2013-04-09 00:58:19

+0

@DylanP然而,在你的情况下,我认为从传递的具体实例中尝试推断所需的接口会更容易。只需检查所有这些接口并找到它们实现的通用接口,然后将它们全部传递给'Proxy.newProxyInstance()'调用。起初我没有意识到'newProxyInstance()'接受了多个接口。让我用这个附加技术更新我的答案。 – 2013-04-09 01:02:31

0

Composite模式很适合你的情况。

AppleListener appleListener1 = new AppleProcessorA(); 
AppleListener appleListener2 = new AppleProcessorB(); 
CompositeListener composite = CompositeListener.for(appleListener1, appleListener2); 
Apple apple = new Apple(); 
apple.setListener(composite); 

您可能需要重构AppleListener和OrangeListener实现一个包含了受通知监听器的方法的Listener接口。 CompositeListener还必须扩展此侦听器以实现组合模式。

相关问题