爪哇。实现监听器的正确模式

IT小君   2021-12-13T01:55:36

非常典型的情况是,给定的对象需要有许多侦听器。例如,我可能有

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

但我会有很多这样的情况。也就是说,我也会有一个Tiger带有TigerListeners对象现在,TigerListeners 和ElephantListeners 完全不同:

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

尽管

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

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

评论(6)
IT小君

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

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

2021-12-13T01:55:36   回复
IT小君

对于来这里只是想成为听众的人来说,这是一个更一般的答案。我正在总结从 CodePath创建自定义侦听器如果您需要更多解释,请阅读该文章。

以下是步骤。

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
    }
}
2021-12-13T01:55:36   回复
IT小君

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

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

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

另一个是java.util.Observable提供广播机制,但恕我直言,这也不是最好的事情。

我喜欢 ElephantListener.onStomp()

2021-12-13T01:55:36   回复
IT小君

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

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

2021-12-13T01:55:37   回复
IT小君

为此,我创建了一个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
2021-12-13T01:55:37   回复
IT小君

试试javakiss库,你会更快、更正确地完成这项工作。

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 - 熟练的 Java 听力(本地副本)

2021-12-13T01:55:37   回复