Question

J'ai une classe java qui déclenche des événements java personnalisé. La structure du code est le suivant:

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) {
  ..
  }
}

Tout fonctionne correctement, mais je voudrais créer une nouvelle sous-classe A (appeler B), qui peut tirer un nouvel événement. Je pense à la modification suivante:

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) {
  ..
  }

}

Est-ce la bonne approche? Je recherche sur le Web pour des exemples, mais n'a pas pu trouver.

Il y a quelques choses que je n'aime pas cette solution:

  1. BListener a deux méthodes, on utilise AEvent les autres utilisations BEvent en tant que paramètre.
  2. B classe à la fois a et addAListener méthodes addBListener. Dois-je cacher addAListener avec mot-clé privée? [Mise à jour: il est impossible de se cacher avec mot-clé private]
  3. Des problèmes similaires avec les méthodes fireAListenerEvent1 et fireBListenerEvent1.

J'utilise Java version 1.5.

Était-ce utile?

La solution

Je ne vois pas une raison pour laquelle devrait prolonger BListener AListener.

Voulez-vous vraiment forcer tout le monde intéressé par les événements à mettre en œuvre B aussi event1()?

vous pouvez pas non plus ajouter addAListener(), car une classe dérivée ne peut pas réduire la visibilité d'une méthode qui est présente dans la classe parente. En outre, vous ne devriez pas avoir, ou vous violer principe de substitution Liskov (tous les must B être capable de faire tout un A peut faire).

Et comme dernière remarque, je ferais les méthodes protégées fire*(). Il n'y a généralement aucune raison de les garder du public et réduire le nombre de membres du public conserve l'interface publique propre.

Autres conseils

Ne pas utiliser d'héritage, de ce n'est pas ce que vous voulez et vous conduire à une fragile et difficile à changer la conception. La composition est une approche plus souple et mieux pour la conception. Essayez toujours de concevoir des interfaces aussi granulaire que possible parce qu'ils ne doivent pas être l'événement a changé. Ils sont votre contrat avec le reste du système. Si de nouvelles fonctionnalités doit être ajoutée la première option est d'ajouter plus d'informations à l'événement. Si ce n'est pas le cas, alors vous devez concevoir une nouvelle interface pour la livraison de cet événement. Cela évite d'avoir à modifier le code existant qui est pas affectée.

Voici mon modèle préféré pour cela, je beleive il est communément appelé un observateur.

Faire une nouvelle interface définissant une des méthodes pour ce type d'événement (fooEvent () addFooEventListener () removeFooEventListener ()). Mettre en œuvre cette interface dans la classe concrète qui génère ces événements. (J'appelle habituellement quelque chose comme ce SourcesFooEvent, FiresFooEvent, FooEventSource, etc)

Si vous voulez réduire la duplication de code que vous pouvez construire une classe d'aide qui gère l'enregistrement des auditeurs, les stocke dans une collection, et fournit une méthode d'incendie pour la publication des événements.

Génériques peut aider ici. Tout d'abord, une interface générique d'écoute:

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

Ensuite, une interface EventSource correspondant:

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

Enfin une classe de base abstraite pour construire rapidement une classe d'aide pour gérer l'enregistrement des auditeurs et l'envoi de l'événement:

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

Vous souhaitez faire usage de la EventDispatcher abstraite par encapsulation, ce qui permet une autre classe pour mettre en œuvre facilement EventSource tout en ne nécessitant pas à étendre une classe particulière.

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

Espérons que cela illustre le bon équilibre entre la sécurité de type (important), la flexibilité et la réutilisation du code.

Je comprends votre commentaire à Saua que le tir B se déclenche automatiquement A.

Pourquoi ne pas utiliser un seul type d'auditeur, puis mélanger l'héritage, la délégation et les génériques?

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

}

Explication: le code de gestion des auditeurs est partagée, dans la classe de base Listener Manager. B ne peut recevoir BListeners en raison de médicaments génériques à la compilation de vérification. Firing B se déclenche automatiquement A.

Il me semble que vous pouvez garder les choses très simples.

Mon understading

  • Vous avez une classe de base A qui effectue une Fonctionnementdebase

  • Vous pouvez avoir une sous-classe plus spécifique B qui peut en outre effectuer un peu plus specificOperations

Si tel est le cas, vous avez besoin que les deux événements soient traités ( de base A et base + spécifique B)

Eh bien, vous n'avez pas besoin de surcharger les méthodes pour le faire, la seule chose que vous devez faire est d'ajouter des gestionnaires spécifiques (ou auditeurs) pour des événements spécifiques.

Il peut être le cas, l'événement est « de base », c'est très bien.

Mais lorsque l'événement est spécifique, vous devez réagir en conséquence. Alors, ce que je ferais est d'ajouter un contrôle l'auditeur spécifique de discriminer la spécifique événement comme celui-ci:

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

Et voilà.

Votre description du problème est trop abstrait, donc pas de solutions concrètes peuvent être proposées. Pourtant, s'il est difficile d'expliquer ce que vous voulez atteindre, vous auriez probablement besoin de re-analyser ce que le problème est en premier lieu.

Si ma compréhension ci-dessus est correcte (que vous devez gérer base + spécifique quelques fois) le long code suivant ci-dessous peut aider.

Cordialement


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

Exemple de sortie:

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"

Pour aller plus loin, vous pouvez même vous débarrasser des interfaces pour le garder plus simple

Si

public class BEvent extends AEvent {
...
}

public interface BListener extends AListener {

  public void event2(BEvent event);
}

ne pouvez pas vous faire comme somethin:

public class B extends A {

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

Comme je l'ai dit dans le commentaire, je ne sais pas ce que vous voulez vraiment atteindre? Qui est appelé d'où, et que faut-il faire quand il appelée?

D'après ce que peu d'informations que nous avons sur la relation entre & A B, je pense qu'il est source de confusion pour faire une sous-interface de BListener AListener. Comme son nom l'indique, un est supposé BEvent écouter s, qui AEvent sont déjà une sous-classe de s ClickableMouseListener. Pour plus de clarté, les auditeurs doivent avoir des objectifs plus exigeants; ils ne doivent pas se chevaucher inutilement. En outre, il n'y a pas besoin de ces auditeurs se chevauchent puisque vous avez déjà défini des méthodes distinctes dans la classe pour gérer différents ClickableMouseWidget types d'auditeurs.

Pour illustrer mon propos, considérons cet exemple, de style après votre code:

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)                                      

Cette conception fonctionne, mais est source de confusion, car les poignées deux types d'<=> événements et gère deux types <=> d'auditeurs, comme vous l'avez souligné à juste titre. Maintenant, pensez à l'alternative suivante qui utilise la composition au lieu de l'héritage:

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top