Java的。用于实现侦听器的正确模式

时间:2010-06-04 16:19:06

标签: java design-patterns listeners

通常我会遇到一个给定对象需要有很多侦听器的情况。例如,我可能有

class Elephant {
  public void addListener( ElephantListener listener ) { ... }
}

但我会遇到很多这样的情况。也就是说,我还会有一个Tiger对象,它会有TigerListener个。现在,TigerListenerElephantListener完全不同:

interface TigerListener {
  void listenForGrowl( Growl qrowl );
  void listenForMeow( Meow meow );
}

,而

interface ElephantListener {
  void listenForStomp( String location, double intensity );
}

我发现我必须不断在每个动物类中重新实现广播机制,并且实现总是相同的。是否有首选模式?

6 个答案:

答案 0 :(得分:26)

不是每个Listener都有可以发送它的每个事件类型的特定方法,而是将接口更改为接受通用Event类。然后,如果需要,可以将Event子类化为特定的子类型,或者使其包含double intensity等状态。

然后

TigerListener和ElephentListener成为

interface TigerListener {
    void listen(Event event);
}

实际上,您可以进一步将此接口重构为普通Listener

interface Listener {
    void listen(Event event);
}

然后,您的Listener实现可以包含他们关心的特定事件所需的逻辑

class TigerListener implements Listener {
    @Overrides
    void listen(Event event) {
        if (event instanceof GrowlEvent) {
            //handle growl...
        }
        else if (event instance of MeowEvent) {
            //handle meow
        }
        //we don't care about any other types of Events
    }
}

class ElephentListener {
    @Overrides
    void listen(Event event) {
        if (event instanceof StompEvent) {
            StompEvent stomp = (StompEvent) event;
            if ("north".equals(stomp.getLocation()) && stomp.getDistance() > 10) { 
                ... 
            }
        }
    }
}

订阅者和发布者之间的关键关系是发布者可以向订阅者发送事件,不一定是它可以向其发送某些类型的事件 - 这种类型的重构将该逻辑从接口推送到具体实施。

答案 1 :(得分:10)

对于那些来这里只想做一个听众的人来说,这是一个更普遍的答案。我在CodePath中总结了Creating Custom Listeners。如果您需要更多解释,请阅读该文章。

以下是步骤。

1。定义接口

这是在需要与某个未知父级进行通信的子类中。

public class MyClass {

    // interface
    public interface MyClassListener {
        // add whatever methods you need here
        public void onSomeEvent(String title);
    }
}

2。创建一个监听器设置器

将私有侦听器成员变量和公共setter方法添加到子类。

public class MyClass {

    // add a private listener variable
    private MyClassListener mListener = null;

    // provide a way for another class to set the listener
    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }


    // interface from Step 1
    public interface MyClassListener {
        public void onSomeEvent(String title);
    }
}

3。触发侦听器事件

子对象现在可以在侦听器接口上调用方法。一定要检查null,因为可能没有人在听。 (也就是说,父类可能没有为我们的侦听器调用setter方法。)

public class MyClass {

    public void someMethod() {
        // ...

        // use the listener in your code to fire some event
        if (mListener != null) 
            mListener.onSomeEvent("hello");
    }


    // items from Steps 1 and 2

    private MyClassListener mListener = null;

    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }

    public interface MyClassListener {
        public void onSomeEvent(String myString);
    }
}

4。在Parent

中实现监听器回调

父级现在可以使用我们在子类中设置的侦听器。

示例1

public class MyParentClass {

    private void someMethod() {

        MyClass object = new MyClass();
        object.setMyClassListener(new MyClass.MyClassListener() {
            @Override
            public void onSomeEvent(String myString) {
                // handle event
            }
        });
    }
}

示例2

public class MyParentClass implements MyClass.MyClassListener {

    public MyParentClass() {
        MyClass object = new MyClass();
        object.setMyClassListener(this);
    }

    @Override
    public void onSomeEvent(String myString) {
        // handle event
    }
}

答案 2 :(得分:3)

我认为你这样做是正确的,因为你的界面具有语义价值并表达他们正在听的内容(例如咆哮和喵喵而不是踩踏)。使用通用方法,您可以重用广播代码,但可能会失去可读性。

例如,java.beans.PropertyChangeSupport是一个用于实现Oberservers监听值变化的实用程序。它执行广播,但您仍需要在域类中实现该方法并委托给PropertyChangeSupport对象。回调方法本身没有意义,广播的事件是基于字符串的:

public interface PropertyChangeListener extends java.util.EventListener {
     void propertyChange(PropertyChangeEvent evt);
}

另一个是java.util.Observable提供广播机制,但它也不是最好的东西。

我喜欢ElephantListener.onStomp()

答案 3 :(得分:1)

Whiteboard Pattern有不同的选项。这使发布者和订阅者彼此断开连接,并且都不包含任何广播代码。它们都只使用pub / sub的消息传递机制,并且没有与另一个直接连接。

这是OSGi平台中消息传递的常用模型。

答案 4 :(得分:1)

我专门为此创建了一个 Signals 库。删除“重新实现广播机制”中涉及的锅炉代码。

信号是从接口自动创建的对象。它具有添加侦听器和调度/广播事件的方法。

看起来像这样:

interface Chat{
    void onNewMessage(String s);    
}

class Foo{
    Signal<Chat> chatSignal = Signals.signal(Chat.class);
    
    void bar(){
        chatSignal.addListener( s-> Log.d("chat", s) ); // logs all the messaged to Logcat
    }
}

class Foo2{
    Signal<Chat> chatSignal = Signals.signal(Chat.class);
    
    void bar2(){
        chatSignal.dispatcher.onNewMessage("Hello from Foo2"); // dispatches "Hello from Foo2" message to all the listeners
    }
}

在本例中,Foo2 是通过 Chat 接口的新消息的广播者。 Foo 然后收听这些并将其记录到 logcat。

  • 请注意,您可以使用的接口没有限制
  • 您还有一些糖 API,用于仅注册第一个广播并立即从所有信号中注销(通过 SignalsHelper

答案 5 :(得分:-1)

尝试使用java kiss库,您可以更快,更准确地完成此操作。

import static kiss.API.*;

class Elephant {
  void onReceiveStomp(Stomp stomp) { ... }
}

class Tiger {
  void onReceiveMeow(Meow meow) { ... }
  void onReceiveGrowl(Growl growl) { ... }
}

class TigerMeowGenerator extends Generator<Meow> {
   // to add listeners, you get: 
   //    addListener(Object tiger); // anything with onReceiveMeow(Meow m);
   //    addListener(meow->actions()); // any lambda
   // to send meow's to all listeners, use 
   //    send(meow)
}

生成器是线程安全且高效的(编写正确的生成器是最难的部分)。这是一个实现的想法 Java Dev. Journal - Skilled Listening in Java (local copy)