zoukankan      html  css  js  c++  java
  • java设计模式-回调、事件监听器、观察者模式

    背景

    关于设计模式,之前笔者写过工厂模式,最近在使用gava ListenableFuture时发现事件监听模型特别有意思,于是就把事件监听、观察者之间比较了一番,发现这是一个非常重要的设计模式,在很多框架里扮演关键的作用。

    回调函数

    为什么首先会讲回调函数呢?因为这个是理解监听器、观察者模式的关键。

    什么是回调函数

    所谓的回调,用于回调的函数。 回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。 有这么一句通俗的定义:就是程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序。程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法。

    举个例子:

    这里有两个实体:回调抽象接口、回调者(即程序a)

    • 回调接口(ICallBack )
    public interface ICallBack {
        public void callBack();
    }
    
    • 回调者(用于调用回调函数的类)
    public class Caller {
        
        public void call(ICallBack callBack){
            System.out.println("start...");
            callBack.callBack();
            System.out.println("end...");
        }
        
    }
    
    • 回调测试:
     public static void main(String[] args) {
           Caller call = new Caller();
           call.call(new ICallBack(){
    
            @Override
            public void callBack() {
                System.out.println("终于回调成功了!");
                
            } 
           });
    
        }
    

    控制台输出:

    start...

    终于回调成功了!

    end...

    还有一种写法

           ICallBack callBackB = new ICallBack(){
            @Override
            public void callBack() {
                System.out.println("终于回调成功了!");
            }           
           };
           call.call(callBackB);
    

    或实现这个ICallBack接口类

    class CallBackC implements ICallBack{
            @Override
            public void callBack() {
                System.out.println("终于回调成功了!");  
            }
           }
    

    有没有发现这个模型和执行一个线程,Thread很像。 没错,Thread就是回调者,Runnable就是一个回调接口。

    new Thread(new Runnable(){
            @Override
            public void run() {
                 System.out.println("回调一个新线程!");  
            }}).start();
    

    Callable也是一个回调接口,原来一直在用。 接下来我们开始讲事件监听器

    事件监听模式

    什么是事件监听器

    监听器将监听自己感兴趣的事件一旦该事件被触发或改变,立即得到通知,做出响应。例如:android程序中的Button事件。

    java的事件监听机制可概括为3点:

    1. java的事件监听机制涉及到 事件源,事件监听器,事件对象 三个组件,监听器一般是接口,用来约定调用方式
    2. 当事件源对象上发生操作时,它将会调用事件监听器的一个方法,并在调用该方法时传递事件对象过去
    3. 事件监听器实现类,通常是由开发人员编写,开发人员通过事件对象拿到事件源,从而对事件源上的操作进行处理

    举个例子

    这里我为了方便,直接使用jdk,EventListener 监听器,感兴趣的可以去研究下源码,非常简单。

    监听器接口

    public interface EventListener extends java.util.EventListener {
        //事件处理
        public void handleEvent(EventObject event);
    }
    
    

    事件对象

    public class EventObject extends java.util.EventObject{
        private static final long serialVersionUID = 1L;
        public EventObject(Object source){
            super(source);
        }
        public void doEvent(){
            System.out.println("通知一个事件源 source :"+ this.getSource());
        }
    
    }
    

    事件源

    事件源是事件对象的入口,包含监听器的注册、撤销、通知

    
    public class EventSource {
       //监听器列表,监听器的注册则加入此列表
        private Vector<EventListener> ListenerList = new Vector<EventListener>();
        //注册监听器
        public void addListener(EventListener eventListener){
            ListenerList.add(eventListener);
        }
        //撤销注册
        public void removeListener(EventListener eventListener){
            ListenerList.remove(eventListener);
        }
     //接受外部事件
        public void notifyListenerEvents(EventObject event){        
            for(EventListener eventListener:ListenerList){
                    eventListener.handleEvent(event);
            }
        }
        
    }
    
    

    测试执行

    public static void main(String[] args) {
            EventSource eventSource = new EventSource();
            
            eventSource.addListener(new EventListener(){
                @Override
                public void handleEvent(EventObject event) {
                    event.doEvent();
                    if(event.getSource().equals("closeWindows")){
                        System.out.println("doClose");
                    } 
                }
                
            });
    
    
            /*
             * 传入openWindows事件,通知listener,事件监听器,
             对open事件感兴趣的listener将会执行
             **/
            eventSource.notifyListenerEvents(new EventObject("openWindows"));
            
    }
    

    控制台显示:

    通知一个事件源 source :openWindows

    通知一个事件源 source :openWindows

    doOpen something...

    到这里你应该非常清楚的了解,什么是事件监听器模式了吧。 那么哪里是回调接口,哪里是回调者,对!EventListener是一个回调接口类,handleEvent是一个回调函数接口,通过回调模型,EventSource 事件源便可回调具体监听器动作。

    有了了解后,这里还可以做一些变动。 对特定的事件提供特定的关注方法和事件触发

    
    public class EventSource {
         ...
    public void onCloseWindows(EventListener eventListener){
            System.out.println("关注关闭窗口事件");
            ListenerList.add(eventListener);
        }
        
        public void doCloseWindows(){
            this.notifyListenerEvents(new EventObject("closeWindows"));
        }
        ...
    }
    
    public static void main(String[] args) {
     EventSource windows = new EventSource();
            /**
             * 另一种实现方式
             */
            //关注关闭事件,实现回调接口
            windows.onCloseWindows(new EventListener(){
    
                @Override
                public void handleEvent(EventObject event) {
                    event.doEvent();
                    if(event.getSource().equals("closeWindows")){
                        System.out.println("通过onCloseWindows来关注关闭窗口事件:并执行成功。 closeWindows");
                    }
                    
                }
                
            });
            
           //窗口关闭动作
            windows.doCloseWindows();
    
    }
    

    这种就类似于,我们的窗口程序,Button监听器了。我们还可以为单击、双击事件定制监听器。

    观察者模式

    什么是观察者模式

    观察者模式其实原理和监听器是一样的,使用的关键在搞清楚什么是观察者、什么是被观察者。

    • 观察者(Observer)相当于事件监器。有个微博模型比较好理解,A用户关注B用户,则A是B的观察者,B是一个被观察者,一旦B发表任何言论,A便可以获得。
    • 被观察者(Observable)相当于事件源和事件,执行事件源通知逻辑时,将会回调observer的回调方法update。

    举个例子

    为了方便,同样我直接使用jdk自带的Observer。

    一个观察者

    public class WatcherDemo implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            if(arg.toString().equals("openWindows")){
                System.out.println("已经打开窗口");
            }
        }
    }
    

    被观察者

    Observable 是jdk自带的被观察者,具体可以自行看源码和之前的监听器事件源类似。

    主要方法有

    • addObserver() 添加观察者,与监听器模式类似
    • notifyObservers() 通知所有观察者

    类Watched.java的实现描述:被观察者,相当于事件监听的事件源和事件对象。又理解为订阅的对象 主要职责:注册/撤销观察者(监听器),接收主题对象(事件对象)传递给观察者(监听器),具体由感兴趣的观察者(监听器)执行

    /**
     * 
     * 类Watched.java的实现描述:被观察者,相当于事件监听的事件源和事件对象。又理解为订阅的对象
     * 主要职责:注册/撤销观察者(监听器),接收主题对象(事件对象)传递给观察者(监听器),具体由感兴趣的观察者(监听器)执行
     * @author xuan.lx 2016年11月22日 下午3:52:11
     */
    public class Watched extends Observable {
    
        public void notifyObservers(Object arg) {
            
            /**
             * 为避免并发冲突,设置了changed标志位changed =true,则当前线程可以通知所有观察者,内部同步块会完了会设置为false;
           通知过程中,正在新注册的和撤销的无法通知到。
             */
            super.setChanged();
            /**
             * 事件触发,通知所有感兴趣的观察者
             */
            super.notifyObservers(arg);
           
        }
        
    }
    

    测试执行

        public static void main(String[] args) {
            Watched watched = new Watched();
            WatcherDemo watcherDemo = new WatcherDemo();
            watched.addObserver(watcherDemo);
            watched.addObserver(new Observer(){
                @Override
                public void update(Observable o, Object arg) {
                    if(arg.toString().equals("closeWindows")){
                        System.out.println("已经关闭窗口");
                    }
                }
            });
            //触发打开窗口事件,通知观察者
            watched.notifyObservers("openWindows");
            //触发关闭窗口事件,通知观察者
            watched.notifyObservers("closeWindows");
    
        }
    

    控制台输出:

    已经打开窗口

    已经关闭窗口

    总结

    从整个实现和调用过程来看,观察者和监听器模式基本一样。

    有兴趣的你可以基于这个模型,实现一个简单微博加关注和取消的功能。 说到底,就是事件驱动模型,将调用者和被调用者通过一个链表、回调函数来解耦掉,相互独立。

    “你别来找我,有了我会找你”。

    整个设计模式的初衷也就是要做到低耦合,低依赖。

    再延伸下,消息中间件是什么一个模型? 将生产者+服务中心(事件源)和消费者(监听器)通过消息队列解耦掉. 消息这相当于具体的事件对象,只是存储在一个队列里(有消峰填谷的作用),服务中心回调消费者接口通过拉或取的模型响应。 想必基于这个模型,实现一个简单的消息中间件也是可以的。

    还比如gava ListenableFuture,采用监听器模式就解决了future.get()一直阻塞等待返回结果的问题。

    有兴趣的同学,可以再思考下观察者和责任链之间的关系, 我是这样看的。

    同样会存在一个链表,被观察者会通知所有观察者,观察者自行处理,观察者之间互不影响。 而责任链,讲究的是击鼓传花,也就是每一个节点只需记录继任节点,由当前节点决定是否往下传。 常用于工作流,过滤器web filter。

    转自:https://my.oschina.net/u/923324/blog/792857

  • 相关阅读:
    多个表单如何同时验证
    vue+element 动态表单验证
    ‘Maximum call stack size exceeded’错误的解决方法
    select下拉框option的样式修改
    vue项目打包之后样式错乱问题,如何处理
    11_我拥有了属于自己的公众号了
    10_更改自己的ID
    001_Spring之xml的class的补全(eclipse)
    01_Navicat的快捷键学习
    web开发资源网站汇总
  • 原文地址:https://www.cnblogs.com/AnXinliang/p/9976252.html
Copyright © 2011-2022 走看看