Наследование Java-слушателя - PullRequest
7 голосов
/ 16 декабря 2008

У меня есть класс Java, который запускает пользовательские события Java. Структура кода следующая:

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

Все работает правильно, но я хотел бы создать новый подкласс A (назовите его B), который может вызвать новое событие. Я думаю о следующей модификации:

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

}

Это правильный подход? Я искал в Интернете примеры, но не смог их найти.

Есть несколько вещей, которые мне не нравятся в этом решении:

  1. BListener имеет два метода: один использует AEvent, другой использует BEvent в качестве параметра.
  2. B имеет методы addAListener и addBListener. Должен ли я скрыть addAListener с частным ключевым словом? [ОБНОВЛЕНИЕ: невозможно скрыть частное ключевое слово]
  3. Аналогичная проблема с fireAListenerEvent1 и fireBListenerEvent1 методами.

Я использую Java версии 1.5.

Ответы [ 6 ]

12 голосов
/ 16 декабря 2008

Не вижу причины, по которой BListener должно расширяться AListener.

Вы действительно хотите, чтобы все заинтересованные в B событиях также реализовали event1()?

Также нельзя добавить addAListener(), поскольку производный класс не может уменьшить видимость метода, присутствующего в родительском классе. Кроме того, вам не нужно это делать, иначе вы бы нарушили принцип подстановки Лискова *1011* (каждый B должен иметь возможность делать все, что может делать A).

И в качестве последнего замечания я бы сделал методы fire*() защищенными. Обычно нет вообще никакой причины держать их публичными, а уменьшение количества публичных участников делает ваш публичный интерфейс чистым.

6 голосов
/ 29 января 2009

Не используйте наследование, это не то, что вы хотите, и это приведет к хрупкости и сложному изменению дизайна Композиция - более гибкий и лучший подход к дизайну. Всегда старайтесь разрабатывать интерфейсы как можно более детально, поскольку они не должны изменяться. Это ваш контракт с остальной системой. Если необходимо добавить новую функциональность, первым вариантом является добавление дополнительной информации к событию. Если это не подходит, вам следует разработать новый интерфейс для доставки этого события. Это предотвращает необходимость изменения любого существующего кода, который не затронут.

Вот мой любимый шаблон для этого, я полагаю, его обычно называют Наблюдателем.

Создайте новый интерфейс, определяющий методы для этого типа события (fooEvent () addFooEventListener () removeFooEventListener ()). Реализуйте этот интерфейс в конкретном классе, который генерирует эти события. (Я обычно называю это что-то вроде SourcesFooEvent, FiresFooEvent, FooEventSource и т. Д.)

Если вы хотите уменьшить дублирование кода, вы можете создать вспомогательный класс, который обрабатывает регистрацию слушателей, сохраняет их в коллекции и предоставляет метод fire для публикации событий.

Здесь могут помочь дженерики. Во-первых, общий интерфейс слушателя:

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

Далее соответствующий интерфейс EventSource:

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

Наконец, абстрактный базовый класс для быстрого создания вспомогательного класса для обработки регистрации слушателей и отправки событий:

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

Вы бы использовали абстрактный EventDispatcher посредством инкапсуляции, позволяя любому другому классу легко реализовать EventSource, не требуя от него расширения какого-либо конкретного класса.

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

Надеюсь, это иллюстрирует хороший баланс между безопасностью типов (важно), гибкостью и повторным использованием кода.

3 голосов
/ 29 января 2009

Мне кажется, вы можете сделать все очень просто.

Мое понимание

  • У вас есть базовый класс A , который выполняет basicOperation

  • У вас может быть более конкретный подкласс B , который, кроме того, может выполнять еще несколько удельных операций

Если это так, вам необходимо обработать оба события ( basic для A и basic + определено для B)

Ну, вам не нужно перегружать методы, чтобы сделать это, единственное, что вам нужно сделать, это добавить определенные обработчики (или слушатели) для определенных событий.

Это может быть случай, когда событие "базовое", это нормально.

Но когда событие конкретное, вы должны реагировать соответствующим образом. Итак, я хотел бы добавить проверку в конкретного слушателя, чтобы различать конкретное событие, подобное этому:

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

И это все.

Ваше описание проблемы слишком абстрактно, поэтому конкретные решения не могут быть предложены. Тем не менее, если трудно объяснить, чего вы хотите достичь, возможно, вам придется заново проанализировать, в чем заключается проблема.

Если мое понимание выше верно (что вам необходимо обработать basic + определенное несколько раз), может помочь следующий длинный код, приведенный ниже.

С уважением


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

Пример вывода:

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"

Если пойти дальше, вы даже можете избавиться от интерфейсов, чтобы упростить его

3 голосов
/ 16 декабря 2008

Из вашего комментария к Сауа я понимаю, что при стрельбе B автоматически запускается A.

Почему бы не использовать один тип слушателя, а затем смешать наследование, делегирование и обобщения?

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

}

Объяснение: код для управления слушателями является общим в менеджере слушателей базового класса. B может получать BListeners только из-за проверки во время компиляции. Стрельба B автоматически выстрелит A.

1 голос
/ 29 января 2009

На основании того небольшого количества информации, которое у нас есть о взаимоотношениях между A и B, я думаю, что путать BListener с подинтерфейсом AListener сложно Как следует из названия, BListener предполагает прослушивание BEvent с, которые уже являются подклассом AEvent с. Для ясности слушатели должны иметь проницательные цели; они не должны пересекаться излишне. Кроме того, нет необходимости в таких перекрывающихся слушателях, поскольку вы уже определили отдельные методы в классе B для обработки разных типов слушателей.

Чтобы проиллюстрировать мою точку зрения, рассмотрим этот пример, стилизованный под ваш код:

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)                                      

Этот дизайн работает, но сбивает с толку, потому что ClickableMouseListener обрабатывает два вида событий, а ClickableMouseWidget обрабатывает два типа слушателей, как вы правильно отметили. Теперь рассмотрим следующую альтернативу, в которой вместо наследования используется композиция:

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
1 голос
/ 27 января 2009

Если

public class BEvent extends AEvent {
...
}

public interface BListener extends AListener {

  public void event2(BEvent event);
}

ты не можешь сделать что-то вроде:

public class B extends A {

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

Как я уже сказал в комментарии, я не уверен, чего вы на самом деле хотите достичь? Кому звонят откуда и что нужно делать, когда его вызывают?

...