Domanda

Ho una classe Java che genera gli eventi Java personalizzato. La struttura del codice è il seguente:

public class AEvent extends EventObject {
...
}

public interface AListener extends EventListener {

  public void event1(AEvent event);

}

public class A {

  public synchronized void addAListener(AListener l) {
  ..
  }

  public synchronized void removeAListener(AListener l) {
  ..
  }

  protected void fireAListenerEvent1(AEvent event) {
  ..
  }
}

Tutto funziona correttamente, ma mi piacerebbe creare una nuova sottoclasse di A (lo chiamano B), che può sparare un nuovo evento. Sto pensando della seguente modifica:

public class BEvent extends AEvent {
...
}

public interface BListener extends AListener {

  public void event2(BEvent event);
}

public class B extends A {

  public synchronized void addBListener(BListener l) {
  ..
  }

  public synchronized void removeBListener(BListener l) {
  ..
  }

  protected void fireBListenerEvent2(AEvent event) {
  ..
  }

}

E 'questo l'approccio corretto? Ero alla ricerca sul web per gli esempi, ma non ho trovato alcuna.

Ci sono alcune cose che non mi piacciono in questa soluzione:

  1. BListener ha due metodi che si usano AEvent gli altri usi BEvent come parametro.
  2. B class sia ha addAListener e addBListener metodi. Dovrei nascondere addAListener con parola chiave privata? [UPDATE: non è possibile nascondere con parola chiave private]
  3. problema simile con fireAListenerEvent1 e fireBListenerEvent1 metodi.

Sto utilizzando Java versione 1.5.

È stato utile?

Soluzione

Non vedo un motivo per cui BListener dovrebbe estendere AListener.

Vuoi davvero di costringere tutti interessati a B eventi per implementare anche event1()?

Inoltre, non è possibile aggiungere addAListener(), dal momento che una classe derivata non può ridurre la visibilità di un metodo che è presente nella classe padre. Inoltre, non dovrebbe essere necessario, o si potrebbe violare il principio di sostituzione (ogni mosto B href="http://en.wikipedia.org/wiki/Liskov_substitution_principle" rel="noreferrer"> essere in grado di fare tutto quello che un a può fare).

E come ultima osservazione, mi piacerebbe fare le fire*() metodi protetti. Di solito c'è nessuna ragione al mondo per tenerli pubblico e la riduzione del numero dei membri pubblici mantiene l'interfaccia pubblici puliti.

Altri suggerimenti

Non utilizzare ereditarietà, non è quello che vuoi e porterà ad una fragili e difficili da cambiare il design. La composizione è un più flessibile e un approccio migliore per la progettazione. Cercare sempre di progettare interfacce come granulare possibile, perché non dovrebbe essere evento modificato. Sono il vostro contratto con il resto del sistema. Se la nuova funzionalità è necessario aggiungere la prima opzione è quella di aggiungere ulteriori informazioni alla manifestazione. Se questo non è appropriato, quindi è necessario progettare una nuova interfaccia per la consegna di tale evento. Ciò evita di dover modificare il codice che non risente.

Ecco la mia modello preferito per questo, ho beleive è comunemente indicato come un osservatore.

Fare una nuova interfaccia che definisce un metodo per quel tipo di evento (fooEvent () addFooEventListener () removeFooEventListener ()). Implementare questa interfaccia nella classe concreta che genera questi eventi. (Io di solito chiama questo qualcosa di simile SourcesFooEvent, FiresFooEvent, FooEventSource, etc)

Se si vuole ridurre la duplicazione del codice è possibile costruire una classe di supporto, che gestisce la registrazione degli ascoltatori, li memorizza in una collezione, e fornisce un metodo di fuoco per la pubblicazione degli eventi.

Generics può aiutare in questo. In primo luogo, un'interfaccia ascoltatore generico:

public interface Listener<T> {
  void event(T event);
}

Avanti, un'interfaccia corrispondente EventSource:

public interface EventSource<T> {
    void addListener(Listener<T> listener);
}

Infine una classe base astratta per costruire rapidamente una classe di supporto per gestire la registrazione di ascoltatori e spedizione evento:

public abstract class EventDispatcher<T> {
    private List<Listener<T>> listeners = new CopyOnWriteArrayList<T>();

    void addListener(Listener<T> listener) {
      listeners.add(listener);
    }    

    void removeListener(Listener<T> listener) {
      listeners.remove(listener);
    }

    void fireEvent(T event) {
      for (Listener<T> listener : listeners) {
        listener.event(event);
      } 
    }
}

Si potrebbe fare uso della EventDispatcher astratta attraverso l'incapsulamento, consentendo ad ogni altra classe di implementare facilmente EventSource pur non richiedendo per estendere una particolare classe.

public class Message {
}

public class InBox implements EventSource<Message> {

  private final EventDispatcher<Message> dispatcher = new EventDispatcher<Message>();

  public void addListener(Listener<Message> listener) {
    dispatcher.addListener(listener);
  }

  public void removeListener(Listener<Message> listener) {
    dispatcher.removeListener(listener);
  }

  public pollForMail() {
    // check for new messages here...
    // pretend we get a new message...

    dispatcher.fireEvent(newMessage);
  }
}

Speriamo che questo illustra il buon equilibrio tra la sicurezza di tipo (importante), la flessibilità e il riutilizzo del codice.

Ho capito da tuo commento Saua che sparo B scatterà automaticamente A.

Perché non usare un solo tipo di ascoltatore e poi mescolare eredità, delega e farmaci generici?

class AEvent {}
class BEvent extends Event{}

interface EventListner<E extends AEvent>
{
   onEvent(E e);
}

class ListenerManager<E extends AEvent>{
    addListner(EventListener<? extends E>){}
    removeListner(EventListener<? extends E>){}
    fire(E e);
}

class A extends ListenerManager<AEvent>
{
}

class B extends ListenerManager<BEvent>
{
   A delegatorA;

  @Override addListener(EventListener<? extends BEvent> l)
  {
    super.addListner(l);
    delegatorA.addListener(l);
  }       

  @Override removeListener(EventListener<? extends BEvent> l)
  {
    super.removeListner(l);
    delegatorA.removeListener(l);
  }       

  @Override fire(BEvent b)
  {
    super.fire(b);
    a.fire(b)
  }

}

Spiegazione: il codice per gli ascoltatori di gestione è condivisa, nella classe di base Listener Manager. B può ricevere solo BListeners causa di farmaci generici in fase di compilazione. Firing B scatterà automaticamente A.

Mi sembra che è possibile mantenere le cose molto semplici.

La mia understading

  • Si dispone di una classe di base A che esegue alcuni basicOperation

  • Si può avere una sottoclasse più specifica B che oltre può procedere con ulteriori specificOperations

Se questo è il caso, è necessario che entrambi gli eventi trattati ( base per A e base + specifica per B)

Beh, non c'è bisogno di sovraccaricare i metodi per farlo, l'unica cosa che dovete fare è aggiungere gestori specifici (o ascoltatori) per eventi specifici.

Può essere il caso l'evento è "di base", va bene.

Ma quando l'evento è specifica, è necessario reagire di conseguenza. Quindi, quello che vorrei fare è quello di aggiungere un controllo l'ascoltatore specifica di discriminare il specifica evento come questo:

        if( whichEvent instanceof SpecificEvent ) { 
            SpecificEvent s = ( SpecificEvent ) whichEvent;
            // Do something specific here...
        }

E questo è tutto.

La tua descrizione del problema è troppo astratto, quindi non soluzioni concrete possono essere proposti. Eppure, se la sua difficile spiegare che cosa si vuole raggiungere, probabilmente si avrebbe bisogno di ri-analizzare quale sia il problema in primo luogo.

Se la mia comprensione di cui sopra è corretto (che è necessario gestire base + specifica alcune volte) il seguente codice lunga al di sotto può aiutare.

Con i migliori saluti


import java.util.*;
class A { 

    // All the listener will be kept here. No matter if basic or specific.
    private List<Listener> listeners = new ArrayList<Listener>();


    public void add( Listener listener ) { 
        listeners.add( listener );
    }
    public void remove( Listener listener ) { 
        listeners.remove( listener );
    }


    // In normal work, this class just perform a basic operation.
    public  void normalWork(){
        performBasicOperation();
    }

    // Firing is just firing. The creation work and the 
    // operation should go elsewhere.
    public void fireEvent( Event e ) { 
        for( Listener l : listeners ) { 
            l.eventHappened( e );
        }
    }

    // A basic operation creates a basic event
    public void performBasicOperation() { 
        Event e = new BasicEvent();
        fireEvent( e );
    }
}

// Specialized version of A.
// It may perform some basic operation, but also under some special circumstances
// it may  perform an specific operation too
class B extends A { 

    // This is a new functionality added by this class.
    // Hence an specifi event is fired.
    public  void performSpecificOperation() {
        Event e = new SpecificEvent();
        // No need to fire in different way
        // an event is an event and that's it.
        fireEvent( e );
    }

    // If planets are aligned, I will perform 
    // an specific operation.
    public  void normalWork(){
        if( planetsAreAligned() ) { 
            performSpecificOperation();
        } else { 
            performBasicOperation();
        }
    }
    private boolean planetsAreAligned() { 
        //return new Random().nextInt() % 3 == 0;
        return true;
    }
}

// What's an event? Something from where you can get event info?
interface Event{
    public Object getEventInfo();
}

// This is the basic event.
class BasicEvent implements Event{
    public Object getEventInfo() {
        // Too basic I guess.
        return "\"Doh\"";
    }
}
// This is an specific event. In this case, an SpecificEvent IS-A BasicEvent.
// So , the event info is the same as its parent. "Doh".
// But, since this is an SpecificEvent, it also has some "Specific" features.
class SpecificEvent extends  BasicEvent {

    // This method is something more specific.
    // There is no need to overload or create 
    // different interfaces. Just add the new  specific stuff
    public Object otherMethod() {
        return "\"All I can say is , this was an specific event\"";
    }
}

// Hey something just happened.
interface Listener { 
    public void eventHappened( Event whichEvent );
}

// The basic listner gets information 
// from the basic event. 
class BasicEventListener implements Listener { 
    public void eventHappened( Event e ) {
            System.out.println(this.getClass().getSimpleName() + ": getting basic functionality: " + e.getEventInfo());
        }
}


// But the specific listner may handle both.
// basic and specific events.
class SpecificListener extends BasicEventListener { 
    public void eventHappened( Event whichEvent ) {
        // Let the base to his work
        super.eventHappened( whichEvent );


        //  ONLY if the event if of interest to THIS object
        // it will perform something extra ( that's why it is specific )
        if( whichEvent instanceof SpecificEvent ) { 
            SpecificEvent s = ( SpecificEvent ) whichEvent;
            System.out.println(this.getClass().getSimpleName() + ": aaand  getting specific functionality too: " + s.otherMethod() );
            // do something specific with s 
        }
    }
}

// See it run. 
// Swap from new A() to new B() and see what happens.
class Client { 
    public static void main( String [] args ) { 
        A a = new B();
        //A a = new A();

        a.add( new BasicEventListener() );
        a.add( new SpecificListener() );

        a.normalWork();
    }
}

Esempio di output:

BasicEventListener: getting basic functionality: "Doh"
SpecificListener: getting basic functionality: "Doh"
SpecificListener: aaand  getting specific functionality too: "All I can say is , this was an specific event"

Prendendo ulteriormente, si può anche sbarazzarsi di interfacce per tenerlo più semplice

Se

public class BEvent extends AEvent {
...
}

public interface BListener extends AListener {

  public void event2(BEvent event);
}

Non si può fare qualcosa di simile:

public class B extends A {

  @Override
  public synchronized void addAListener(AListener l) {
    if (l instanceof BListener) {
       ...
    } else {
       super.addAListener(l);
    }
  }
  ...
}

Come ho detto nel commento, non sono sicuro che cosa realmente vuole raggiungere? Chi è chiamato da dove, e che cosa ha bisogno di fare quando vieni chiamato?

In base a quanto poche informazioni che abbiamo circa il rapporto tra A & B, penso che sia fonte di confusione per fare BListener un sottointerfaccia di AListener. Come suggerisce il nome, un BEvent è supponiamo di ascoltare AEvent s, che sono già una sottoclasse di ClickableMouseListener s. Per chiarezza, gli ascoltatori dovrebbero avere scopi esigenti; non dovrebbero sovrapporsi inutilmente. Inoltre, non v'è alcuna necessità di tali ascoltatori sovrapposti dal momento che già definire i metodi separati in classe ClickableMouseWidget per gestire diversi tipi di ascoltatori.

Per illustrare il mio punto, considerare questo esempio, stile dopo il vostro codice:

public class MovableMouseEvent extends EventObject

public class ClickableMouseEvent extends MovableMouseEvent

public interface MovableMouseListener extends EventListener
  // mouseMoved(MovableMouseEvent)

public interface ClickableMouseListener extends MovableMouseListener 
  // mouseClicked(ClickableMouseEvent) 

public class MovableMouseWidget
  // {addMovableMouseListener,removeMovableMouseListener}(MovableMouseListener)
  // fireMovableMouseEvent(MovableMouseEvent)                           

public class ClickableMouseWidget extends MovableMouseWidget
  // {addClickableMouseListener,removeClickableMouseListener}(ClickableMouseListener)
  // fireClickableMouseEvent(ClickableMouseEvent)                                      

Questo progetto funziona, ma è confusa perché <=> gestisce due tipi di eventi, e <=> gestisce due tipi di ascoltatori, come hai giustamente fatto notare. Ora, si consideri la seguente alternativa che utilizza la composizione, invece di eredità:

public class MouseMoveEvent extends EventObject // note the name change

public class MouseClickEvent extends EventObject // don't extend MouseMoveEvent 

public interface MouseMoveListener extends EventListener
  // mouseMoved(MouseMoveEvent)

public interface MouseClickListener extends EventListener // don't extend MouseMoveListener 
  // mouseClicked(MouseClickEvent) 

public interface MouseMoveObserver
  // {addMouseMoveListener,removeMouseMoveListener}(MouseMoveListener)
  // fireMouseMoveEvent(MouseMoveEvent)

public interface MouseClickObserver
  // {addMouseClickListener,removeMouseClickListener}(MouseClickListener)
  // fireMouseClickEvent(MouseClickEvent)

public class MovableMouseWidget implements MouseMoveObserver

public class ClickableMouseWidget implements MouseMoveObserver, MouseClickObserver
scroll top