NO.23 再探回调:自己动手实现事件监听器模式

图1

事件监听器模式是什么?

采用Java Swing、Android、QT等技术进行实现图形用户程序(GUI)过程中,事件处理是非常重要且绕不开的编码工作,老铁们在利用上述语言框架提供便利的事件处理机制的时候,我们想过背后的运行原理是什么没?或者更进一步我们想过自己来实现一个这样的事件处理机制没?如果没有也没有关系,因为今天我们要一起来实现上述目标。

事件监听器模式是一种最通用且经典的事件处理机制,我们以Java处理事件为例一起来看看,其他如Android、QT的实现方式是类似的,此处不再赘述。

任何支持GUI的操作环境都要不断地监听键盘按键或鼠标点击这样的事件。操作环境将这些事件报告给正在运行的应用程序。如果有事件产生,每个应用程序将决定如何对它们做出响应。

对上述事件处理过程,我们可以进行一个抽象:事件源(例如:按钮或滚动条)的本身被注册了事件监听器,当事件源发生了一个事件(例如:点击或滚动),它会将该事件的相关信息封装在一个相应类型的事件对象中并以通知的形式发送给所有注册的所有事件监听器对象,事件监听器对象可以对该通知进行响应。

敲黑板

  1. 事件监听机制包括了事件源、事件监听器、事件对象三个要素。
  2. 事件源是一个能够注册监听器对象并发送事件对象的对象。
  3. 当事件源发生操作事件时,它会将事件对象传递给所有注册的监听器。
  4. 事件监听器对象是一个实现了特定监听接口的类的实例,通常由开发人员自行实现。它将对事件对象进行处理,利用事件对象中的信息决定如何对事件做出响应。

如何实现事件监听器模式?

按照上述“敲黑板”的描述,我们可以实现事件监听器模式,如代码1-4。

代码1:

//事件监听接口
//规约事件响应接口,传递事件对象EventObject
public interface IEventListener {
    void response(Object sender, EventObject evt);
}

代码2:

import java.util.HashMap;
import java.util.Map;

public class EventObject {
    //事件名称
    protected String name;

    //事件属性集合
    protected Map<String, Object> properties;

    //简单构造函数
    public EventObject(String name){
        this(name, (Object[]) null);
    }

    //复杂构造函数
    //name:事件名称
    //args:属性集合键值对依次排列存放,key1,v1,key2,v2,key3,v3
    public EventObject(String name, Object... args){
        this.name = name;
        properties = new HashMap<String, Object>();

        if (args != null){
            for (int i = 0; i < args.length; i += 2){
                if (args[i + 1] != null){
                    properties.put(String.valueOf(args[i]), args[i + 1]);
                }
            }
        }
    }

    //获取事件名称
    public String getName(){
        return name;
    }

    //获取事件对象的属性列表
    public Map<String, Object> getProperties(){
        return properties;
    }

    //根据特定属性关键字,获取相应属性值
    public Object getProperty(String key){
        return properties.get(key);
    }
}

代码3:

//定义事件源
//管理监听器注册、注销
//触发事件后告知监听器对象
public class EventSource{
    //监听器列表
    private Vector<IEventListener> eventListeners =
            new Vector<IEventListener>();

    //注册监听器
    public void addListener(IEventListener eventListener){
        eventListeners.add(eventListener);
    }

    //注销监听器
    public void removeListener(IEventListener eventListener){
        eventListeners.remove(eventListener);
    }

    //触发外部事件
    public void fireEvent(EventObject event){        
        for(IEventListener eventListener:eventListeners){
                eventListener.response(this,event);
        }
    }
}

代码4:

//测试客户端
public class TestEvent {
    public static void main(String[] args) {
    EventSource eventSource = new EventSource();
    //事件源注册事件监听器
    eventSource.addListener(new IEventListener() {
        @Override
        public void response(Object sender, EventObject evt) {
            if (evt.getName().equals("LeftMouse-click")) {
                System.out.println("Left mouse clicked!");
            }
        }
    });

    //触发外部事件
    eventSource.fireEvent(new EventObject("LeftMouse-click",
                "locx",23,"locy",56));
    }
}

看看代码4,是不是觉得有点似曾相识的感觉?是的!老铁没有看错,这样的调用模式像极了我们上篇文章中讲到的利用Runnable接口创建Thread的方式。是的——又是一个回调!事件监听器模式又是一个采用回调技术实现的典型设计模式。IEventListener是回调接口,事件响应函数response是回调函数。

但是,老铁们想想上述事件监听器模式实现的方式是否足够通用?有其他更好的改进办法吗?请老铁们将自己的心得写在留言区与大家共同交流。

转载自公众号:代码荣耀
图1

猜你喜欢

转载自blog.csdn.net/maijia0754/article/details/80604444
今日推荐