2013-04-08 15 views
5

Ho molti listener registrati con i metodi setListener, piuttosto che addListener. Quindi, per consentire a più ascoltatori di registrarsi su un oggetto, devo usare multiplexer. Va bene, ma ora devo creare un multiplexer per ogni interfaccia listener che ho. Quindi la mia domanda è: è possibile implementare Mux.create() come richiesto per il codice seguente?È possibile scrivere un multiplexer per scopi generici in 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) { } 
} 

Immagino che questo sia possibile utilizzando la riflessione. C'è qualche ragione per usare la riflessione sarebbe una cattiva idea? (le prestazioni mi vengono in mente)

+0

Se tutti gli ascoltatori implementano l'interfaccia 'AppleListener', non vedo la necessità di a) riflessione, né, b) generici. Prendili tutti e aggiungili ad un 'Elenco ' nel tuo 'Mux' da qualche parte e itera. O mi sta sfuggendo qualcosa? –

+0

Puoi spiegare di più su cosa ti passa per il multiplexer? Perché la stessa cosa mi è venuta in mente come ha detto AlistairIsraele. –

+2

Anche se è leggermente al di fuori di quanto previsto, è possibile utilizzare una classe [Proxy] (http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html). Questo crea un oggetto che assomiglia alla classe data ma chiama un gestore per elaborare le sue chiamate, il gestore può quindi iterare attraverso gli ascoltatori. – BevynQ

risposta

9

È possibile utilizzare una dinamica Proxy.

Il modo più semplice è anche di passare nell'interfaccia desiderata come primo parametro della chiamata Mux.create(). In caso contrario, sarà necessario utilizzare la riflessione per tentare di indovinare l'interfaccia desiderata da tutte le istanze di listener concrete fornite (difficile determinare se tutti gli oggetti listener implementano più interfacce in comune).

Ecco il corto di esso:

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); 
    } 
} 

Quale si usa, per esempio, come segue:

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 

Questo funziona semplicemente creando una dinamica Proxy che è lanciato al tipo di destinazione T. Tale proxy utilizza uno InvocationHandler che semplicemente delega tutte le chiamate di metodo al proxy a determinate istanze concrete.

Nota che mentre, in generale, io finalizzare tutti i parametri e le variabili locali, ove possibile, ho finalizzato solo T... instances in questo caso per evidenziare il fatto che se fosse instancesnon finale, quindi riferimento entro una classe interna anonima no essere permesso (si otterrà un "Impossibile riferirsi a una variabile non finale args all'interno di una classe interna definita in un metodo diverso").

noti inoltre che quanto sopra presuppone che le chiamate al metodo attuale non restituiscono alcun valori significativi (o utili), quindi la handler restituisce anche null per tutte le chiamate di metodo. Dovrai aggiungere un bel po 'di codice in più se vuoi raccogliere i valori di ritorno e restituirli anche in modo significativo.


In alternativa, si può controllare tutto dato instances per determinare le interfacce comuni tutti implementano, e passare tutti quelli newProxyInstance(). Questo rende Mux.create() molto più comodo da usare, con la perdita di un certo controllo sul suo comportamento.

/** 
* @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

Grazie, apprezzo l'accuratezza della tua risposta. Una domanda per curiosità: potremmo dedurre l'interfaccia dal parametro generico 'T', piuttosto che passarla come un altro argomento? –

+0

@DylanP Non si può dedurre nulla dal parametro generico 'T', perché non è 'reificato', cioè quel parametro non è realmente disponibile in fase di esecuzione. Esistono tuttavia alcuni casi limite o trucchi che consentono di dedurre informazioni di tipo generico in fase di esecuzione: in [sottoclassi] (http://alistairisrael.wordpress.com/2009/05/28/introducing-magictest/) e usando [tipo token] (http://www.jquantlib.org/index.php/Using_TypeTokens_to_retrieve_generic_parameters). –

+0

@DylanP Nel tuo caso, tuttavia, penso che sarebbe più semplice provare a dedurre l'interfaccia desiderata dalle istanze concrete passate. Basta ispezionarli tutti e trovare l'interfaccia comune che tutti implementano, quindi passarli tutti alla chiamata 'Proxy.newProxyInstance()'. All'inizio non mi ero reso conto che 'newProxyInstance()' accettava più interfacce. Lascia che aggiorni la mia risposta con questa tecnica aggiuntiva. –

0

composite funziona bene per il vostro caso.

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

Potrebbe essere il refactoring AppleListener e OrangeListener per implementare un'interfaccia Listener che contiene un metodo per il soggetto di notificare gli ascoltatori. CompositeListener dovrebbe anche estendere questo listener per implementare il pattern composito.

Problemi correlati