2015-10-06 9 views
16

Molte classi utilizzano codice simile al seguente per attivare gli ascoltatori.Gli ascoltatori dovrebbero essere in grado di rimuovere gli ascoltatori?

private List<Listener> listeners = new ArrayList<Listener>(); 

public void fireListener() { 
    for(Listener l : listeners) l.someMethod(); 
} 

Questo funziona fino a quando il listener tenta di aggiungere/rimuovere un listener. Questa modifica dall'interno della lista provoca uno ConcurrentModificationException. Dovremmo gestire questo caso o modificare gli ascoltatori non essere valido? Quale sarebbe il modo migliore per gestire l'aggiunta/rimozione degli ascoltatori?

Aggiornamento:
Ecco una possibile soluzione.

public void fireListener() { 
    for(Listener l : listeners.toArray(new Listener[listeners.size()])) { 
    if(!listeners.contains(l)) continue; 
    l.someMethod(); 
    } 
} 
+1

Si prega di inviare una domanda più dettagliata con uno snippet di codice più grande per essere chiari. –

+0

Non c'è 'listeners' una sorta di collezione (come una lista)? Se questo è il caso, [sappiamo di rimuovere quelli con un 'Iterator'.] (Http://stackoverflow.com/q/223918/1079354) – Makoto

+0

Forse non sto rispondendo alla domanda, ma generalmente usando un FOR per rimuovere gli elementi di solito finiscono con un'eccezione. Dovresti usare un Iterator per rimuoverlo mentre si itera. – spekdrum

risposta

14

ci sono tre casi:

  1. Non si desidera consentire la modifica della collezione ascoltatori durante l'esecuzione ascoltatori:
    Un ConcurrentModificationException sarebbe appropriato in questo caso.

  2. si desidera consentire la modifica degli ascoltatori, ma i cambiamenti non devono essere riflesse nel periodo corrente:
    È necessario assicurarsi che una modifica del listeners non ha impatto sul funzionamento corrente. A CopyOnWriteArrayList fa il trucco. Prima di usarlo leggere l'API, ci sono alcune insidie.
    Un'altra soluzione sarebbe copiare l'elenco prima di iterarlo attraverso di esso.

  3. Le modifiche a listeners riflettono all'interno della corsa corrente:
    Il caso più complicato. Usando for-each loop e iterator non funzionerà qui (come hai notato ;-)). Devi tenere traccia dei cambiamenti da solo.
    Una soluzione possbile sarebbe memorizzare ascoltatori in un ArrayList e passare attraverso quella lista usando uno standard per ciclo:

    for (int i =0; i < listeners.size(); i++) { 
        Listener l = listeners.get(i); 
        if (l == null) 
         continue; 
        l.handleEvent(); 
    } 
    

    Rimozione ascoltatori avrebbero impostare l'elemento al suo posto nella matrice null. I nuovi listener vengono aggiunti alla fine e pertanto verranno eseguiti nell'esecuzione corrente.
    Si noti che questa soluzione è solo un esempio e non a prova di thread! Forse è necessario un po 'di manutenzione per rimuovere gli elementi null a volte per impedire che la lista diventi troppo grande.

Spetta a voi decidere cosa è necessario.

Il mio preferito sarebbe il secondo. Permette modifiche durante l'esecuzione ma non modifica il comportamento della corsa corrente che può causare risultati imprevisti.

+0

annuncio 3) iterando dall'ultimo al primo elemento si allevierebbe la necessità della gestione 'null' – mucaho

+1

Concordato: gli ascoltatori vengono di solito aggiunti/rimossi raramente, ma possono essere ripetuti più volte, quindi si adattano bene ai casi d'uso idiomatici di un' CopyOnWriteArrayList'. – Marco13

2

Si può eseguire una copia di listeners Collection per evitare possibili ConcurrentModificationException:

public void fireListener() { 
    Listener[] ll = listeners.toArray(new Listener[listeners.size()]); 
    for(Listener l : ll) l.someMethod(); 
} 
-1

Non è possibile aggiungere/rimuovere a un elenco/carta che viene utilizzata. In C# c'è una bella classe chiamata ConcurrentList/ConcurrentDictionary.

In Java questo è possibile, ecco un po 'di legame piacevole per voi:

https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html

Dal momento che una raccolta viene utilizzato all'interno di un ciclo, la collezione viene utilizzato per un certo periodo di tempo. Se aggiungi/rimuovi nella funzione chiamata all'interno del ciclo, riceverai un errore perché non ti è consentito farlo senza Concurrent.

+3

Perché dovrei preoccuparmi di C# qui? :) – Maroun

+1

Questo non risponde alla domanda. – screenmutt

+0

Pensavo che le raccolte concorrenti non fossero in Java, ma apparentemente lo sono. Ho dimenticato di rimuovere la parte C# in quanto non è necessaria per la risposta. E @screenmutt ho quindi frainteso la domanda. –

9

Credo che rimuovere gli ascoltatori mentre li sparano si sente come un odore di codice e dovrebbe essere evitato.


Tuttavia, anche se, per rispondere alla tua domanda il meglio che posso:
A mio parere, il modo migliore per gestire l'aggiunta/rimozione di ascoltatori durante la navigazione loro sarebbe quella di utilizzare un iteratore che supporta questa.

Ad esempio:

// Assuming the following: 
final List<Listener> listeners = ... 

final Iterator<Listener> i = listeners.iterator(); 
while (i.hasNext()) { 
    // Must be called before you can call i.remove() 
    final Listener listener = i.next(); 
    // Fire an event to the listener or whatever... 

    i.remove(); // that's where the magic happens :) 
} 

Si prega di notare che alcune Iteratorsnon supportano il metodoIterator#remove!

+1

Questo non è molto utile in quanto il listener non si sta rimuovendo da solo qui, che è la domanda. – screenmutt

+5

È piuttosto difficile "rimuovere se stessi" dagli elenchi di dati interni di un altro oggetto, per essere onesti! Per favore considera la prima parte della mia risposta, che è probabilmente un po 'deludente ma molto utile, o la risposta di @ biziclop per capire come puoi usare il mio codice per un ascoltatore per "rimuovere se stesso". Saluti! – ccjmne

5

Direi che non è una buona idea permettere agli ascoltatori di aggiungere/rimuovere altri ascoltatori (o loro stessi). Indica una scarsa separazione delle preoccupazioni. Dopo tutto, perché l'ascoltatore dovrebbe sapere qualcosa sul chiamante? Come testeresti un sistema così strettamente accoppiato?

Invece, è possibile che il metodo di gestione degli eventi (nel listener) restituisca un flag booleano che indica che il listener non desidera ricevere più eventi. Ciò rende la rimozione da parte del dispatcher dell'evento e copre la maggior parte dei casi di utilizzo in cui è necessario modificare l'elenco degli ascoltatori da un listener.

La principale differenza in questo approccio è che l'ascoltatore dice semplicemente qualcosa su se stesso (ad esempio "Non voglio più eventi") piuttosto che essere legato all'implementazione del dispatcher. Questo disaccoppiamento migliora la testabilità e non espone l'interno di nessuna delle due classi all'altra.

public interface FooListener { 
    /** 
    * @return False if listener doesn't want to receive further events. 
    */ 
    public boolean handleEvent(FooEvent event); 
} 

public class Dispatcher { 

    private final List<FooListener> listeners; 

    public void dispatch(FooEvent event) { 
     Iterator<FooListener> it = listeners.iterator(); 
     while (it.hasNext()) { 
     if (!it.next().handleEvent(event)) 
      it.remove(); 
     } 
    } 
} 

Aggiornamento: Aggiungere e rimuovere altri ascoltatori dall'interno di un ascoltatore è leggermente più problematico (e dovrebbe scattare campanelli d'allarme ancora più forte), ma si può seguire un modello simile: l'ascoltatore deve restituire informazioni su quali altri gli ascoltatori devono essere aggiunti/rimossi e il dispatcher deve agire su tali informazioni.

Tuttavia, in questo scenario si ottiene un bel paio di casi limite:

  • quello che dovrebbe accadere con l'evento attuale?
  • Dovrebbe essere inviato agli ascoltatori appena aggiunti?
  • Dovrebbe essere spedito a chi sta per essere rimosso?
  • Cosa succede se si sta tentando di rimuovere qualcosa che è in precedenza nell'elenco e l'evento è già stato inviato ad esso?
  • Inoltre, cosa succede se il listener X aggiunge l'ascoltatore Y e quindi l'ascoltatore X viene rimosso? Dovresti andare con lui?

Tutti questi problemi derivano dal modello di listener stesso e dall'assunto di base che tutti gli ascoltatori nella lista saranno indipendenti l'uno dall'altro. E la logica per gestire questi casi dovrebbe sicuramente andare nel dispatcher e non nell'ascoltatore.

Aggiornamento 2: Nel mio esempio ho usato un booleano nudo per brevità ma nel codice reale definirei un tipo enum a due valori per rendere il contratto più esplicito.

Problemi correlati