2013-04-08 98 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還必須擴展此偵聽器以實現組合模式。

相關問題