【问题标题】:Java Listener inheritanceJava 监听器继承
【发布时间】:2008-12-16 08:17:25
【问题描述】:

我有一个触发自定义 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 类都有 addAListeneraddBListener 方法。我应该用 private 关键字隐藏 addAListener 吗? [更新:无法使用 private 关键字隐藏]
  3. fireAListenerEvent1fireBListenerEvent1 方法存在类似问题。

我使用的是 Java 1.5 版。

【问题讨论】:

  • 您能否详细介绍一下您的设计动机?为什么B扩展A很重要,在什么样的情况下会使用这些东西?否则 uzhin 的方法似乎指向了正确的方向。
  • B 向 A 类添加了新功能。这意味着 A 触发的事件 (event1) 对 B 类是有效事件。由于新功能,新事件 (event2) 对类也有效B. 我想一起处理这两个事件(event1+event2),因为它们是相关的。
  • 旁注:在 fireXXX 方法中可能会发生 ConcurrentModificationException,除非您也同步​​这些方法(昂贵!)。对于典型用例,将 java.util.concurrent.CopyOnWriteArrayList 用于侦听器列表会更快且更不容易出错,这样就不需要同步了。

标签: java inheritance events listener


【解决方案1】:

我看不出BListener 应该扩展AListener 的理由。

你真的想强迫对B事件感兴趣的每个人也实现event1()吗?

您也不能添加addAListener(),因为派生类不能降低父类中存在的方法的可见性。另外,你不应该这样做,否则你会违反Liskov substitution principle(每个 B 都必须能够做 A 可以做的一切)。

最后,我将保护fire*() 方法。通常完全没有理由将它们公开,减少公开成员的数量可以保持您的公开界面整洁。

【讨论】:

  • 感谢您的发言,我已经修改了问题。是的,我想强迫每个人都实施 event1(如果可能的话)。 B 给 A 添加功能,B 不仅会触发 event2,还会触发 event1。
  • 但是为什么呢?谁告诉你每个对event2感兴趣的人同时对event1感兴趣?当您向组件添加侦听器支持时,您通常不知道它将用于什么,因此您不应该强制这种组合。
【解决方案2】:

不要使用继承,这不是你想要的,并且会导致设计脆弱且难以更改。组合是一种更灵活、更好的设计方法。始终尝试尽可能精细地设计界面,因为它们不应该被更改事件。它们是您与系统其余部分的合同。如果需要添加新功能,第一个选项是向事件添加更多信息。如果这不合适,那么您应该设计一个新界面来传递该事件。这样可以避免更改任何不受影响的现有代码。

这是我最喜欢的模式,我相信它通常被称为观察者。

创建一个新接口,为该事件类型定义方法 (fooEvent() addFooEventListener() removeFooEventListener())。在生成这些事件的具体类中实现此接口。 (我通常称之为 SourcesFooEvent、FiresFooEvent、FooEventSource 等)

如果你想减少代码重复,你可以构建一个帮助类来处理监听器的注册,将它们存储在一个集合中,并提供一个用于发布事件的触发方法。

泛型可以在这里提供帮助。首先,一个通用的监听器接口:

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

希望这能说明类型安全(重要)、灵活性和代码重用之间的良好平衡。

【讨论】:

  • 如果你打算使用 Java 5 的特性,那么你应该为 EventDispatcher 使用 CopyOnWriteArrayList 而不是 ArrayList 以避免并发问题
  • 马克,我想你可能想要新的 CopyOnWriteArrayList>();反而。否则代码不会编译。
【解决方案3】:

我从您对 saua 的评论中了解到,解雇 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 只能接收 BListener。 触发 B 会自动触发 A。

【讨论】:

  • 使用泛型听起来很有希望,但在这个解决方案中 B 不是 A 的子类,这是一个很大的缺点。
  • 它们都是ListenerManager的子类,所有的方法都在这里定义。这和从 A 子类化不一样吗?
【解决方案4】:

在我看来,你可以让事情变得非常简单。

我的理解

  • 你有一个基本类A,它执行一些basicOperation

  • 您可能有一个更具体的子类B,它还可以执行一些更具体的具体操作

如果是这种情况,您需要同时处理这两个事件(basic 用于 A 和 basic + specific 用于 B)

好吧,您不需要重载方法来执行此操作,您唯一需要做的就是为特定事件添加特定的处理程序(或侦听器)。

事件可能是“基本的”,这很好。

但是当事件是特定的时,你需要做出相应的反应。所以,我要做的是添加一个检查 in 特定的侦听器来区分 specific 事件,如下所示:

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

就是这样。

您对问题的描述过于抽象,因此可能无法提出具体的解决方案。但是,如果很难解释您想要实现的目标,那么您可能首先需要重新分析问题所在。

如果我上面的理解是正确的(你需要处理 basic + specific 有时)下面的冗长代码可能会有所帮助。

最好的问候


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"

更进一步,您甚至可以去掉接口以使其更简单

【讨论】:

    【解决方案5】:

    如果

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

    正如我在评论中所说,我不确定您真正想要实现什么?从哪里调用谁,调用时需要做什么?

    【讨论】:

      【解决方案6】:

      基于我们掌握的关于AB 之间关系的少量信息,我认为将BListener 设为AListener 的子接口令人困惑。顾名思义,BListener 应该监听BEvents,它们已经AEvents 的子类。为清楚起见,听众应该有明确的目的;它们不应该不必要地重叠。此外,由于您已经在 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-21
        • 1970-01-01
        • 2016-08-16
        • 1970-01-01
        • 1970-01-01
        • 2010-09-27
        • 2012-04-29
        • 2012-06-01
        相关资源
        最近更新 更多