• 从零开始理解JAVA事件处理机制(2)


    第一节中的示例过于简单《从零开始理解JAVA事件处理机制(1)》,简单到让大家觉得这样的代码简直毫无用处。但是没办法,我们要继续写这毫无用处的代码,然后引出下一阶段真正有益的代码。

    一:事件驱动模型初窥

    我们要说事件驱动模型是观察者模式的升级版本,那我们就要说说其中的对应关系:

    观察者对应监听器(学生)

    被观察者对应事件源(教师)

    事件源产生事件,事件带有事件源,监听器监听事件。爱钻牛角尖的朋友可能会说,我擦,什么叫产生事件,监听事件,事件事件到底什么?

    莫慌,如果我们用代码来说事,事件它就是个类,事件源也是个类。这里面一共牵扯到四个类,事件源(即教师、即被观察者)、事件(是一个类,见下文,一般我们以Event或者EventObject命名结尾)、监听器接口、具体的监听器(即学生、即观察者)。

    就像我们上一篇文章中的第一节提到的一样,JDK中当然有现成的事件模型类,我们不妨来一个一个的查看一下吧。

    首先看监听器(即学生、即观察者,大家不要嫌我烦,不停滴括号提醒,这是为了不停滴给大家加深印象),

    package java.util;

    /**
    * A tagging interface that all event listener interfaces must extend.
    * @since JDK1.1
    */
    public interface EventListener {
    }

    简单到不能再简单了,对吧,甚至连一个声明的方法都没有,那它存在的意义在哪?还记得面向对象中的上溯造型吗,所以它的意义就在于告诉所有的调用者,我是一个监听器。

    再来看看事件,即Event或EventObject结尾的那个类,里面含有getSource方法,返回的就是事件源(即教师、即被观察者),

    package java.util;

    /**
    * <p>
    * The root class from which all event state objects shall be derived.
    * <p>
    * All Events are constructed with a reference to the object, the "source",
    * that is logically deemed to be the object upon which the Event in question
    * initially occurred upon.
    *
    * @since JDK1.1
    */

    public class EventObject implements java.io.Serializable {

        private static final long serialVersionUID = 5516075349620653480L;

        /**
         * The object on which the Event initially occurred.
         */
        protected transient Object  source;

        /**
         * Constructs a prototypical Event.
         *
         * @param    source    The object on which the Event initially occurred.
         * @exception  IllegalArgumentException  if source is null.
         */
        public EventObject(Object source) {
            if (source == null)
                throw new IllegalArgumentException("null source");

            this.source = source;
        }

        /**
         * The object on which the Event initially occurred.
         *
         * @return   The object on which the Event initially occurred.
         */
        public Object getSource() {
            return source;
        }

        /**
         * Returns a String representation of this EventObject.
         *
         * @return  A a String representation of this EventObject.
         */
        public String toString() {
            return getClass().getName() + "[source=" + source + "]";
        }
    }

    这个类也很简单,如果说观察者模式中的上层类和结果还带了不少逻辑不少方法的话,那么事件驱动模型中的上层类和接口简直看不到任何东西。没错,

    事件驱动模型中,JDK的设计者们进行了最高级的抽象,就是让上层类只是代表了:我是一个事件(含有事件源),或,我是一个监听者!

    二:老师布置作业的事件驱动模型版本

    老规矩,让我们先给出类图:

    image

    然后,代码实现之:

    观察者接口(学生)。由于在事件驱动模型中,只有一个没有任何方法的接口,EventListener,所以,我们可以先实现一个自己的接口。为了跟上一篇的代码保持一致,在该接口中我们声明的方法的名字也叫update。注意,我们当然也可以不取这个名字,甚至还可以增加其它的方法声明,全看我们的业务需要。

    package com.zuikc.events;

    import java.util.Observable;

    public interface HomeworkListener extends java.util.EventListener {

        public void update(HomeworkEventObject o, Object arg);
    }

    继而实现观察者类(学生),如下:

    package com.zuikc.events;

    public class Student implements HomeworkListener{
        private String name;
        public Student(String name){
            this.name = name;
        }
        @Override
        public void update(HomeworkEventObject o, Object arg) {
            Teacher teacher = o.getTeacher();
            System.out.printf("学生%s观察到(实际是被通知)%s布置了作业《%s》 ", this.name, teacher.getName(), arg);
        }

    }

    对比一下上篇,有变动没?

    继而实现事件子类,如下:

    package com.zuikc.events;

    public class HomeworkEventObject extends java.util.EventObject {

        public HomeworkEventObject(Object source) {
            super(source);
        }
        public HomeworkEventObject(Teacher teacher) {
            super(teacher);
        }
        public Teacher getTeacher(){
            return (Teacher) super.getSource();
        }

    }

    在这个类中,指的关注的就是这个getTeacher方法,它封装了父类EventObject的getSource方法,得到了事件源。理论上,我们使用父类的getSource方法也可行,但是重新在子类封装一下,可读性更强一点。

    然后呢,然后就是我们的教师类,教师类就是事件源,如下:

    package com.zuikc.events;

    import java.util.*;

    public class Teacher {
        private String name;
        private List<String> homeworks;
        /*
         * 教师类要维护一个自己监听器(学生)的列表,为什么?
         * 在观察者模式中,教师是被观察者,继承自java.util.Observable,Observable中含了这个列表
         * 现在我们没有这个列表了,所以要自己创建一个
         */
        private Set<HomeworkListener> homeworkListenerList;

        public String getName() {
            return this.name;
        }

        public Teacher(String name) {
            this.name = name;
            this.homeworks = new ArrayList<String>();
            this.homeworkListenerList  = new HashSet<HomeworkListener>();
        }

        public void setHomework(String homework) {
            System.out.printf("%s布置了作业%s ", this.name, homework);
            homeworks.add(homework);
            HomeworkEventObject event = new HomeworkEventObject(this);
            /*
             * 在观察者模式中,我们直接调用Observable的notifyObservers来通知被观察者
             * 现在我们只能自己通知了~~
             */
            for (HomeworkListener listener : homeworkListenerList) {
                listener.update(event, homework);
            }

        }
        public void addObserver(HomeworkListener homeworkListener){
            homeworkListenerList.add(homeworkListener);
        }

    }

    这个类稍微长了那么一点点,有几个地方值得注意:

    第一处地方,Teacher没有父类了,Teacher作为事件中的事件源Source被封装到HomeworkEventObject中了。这没有什么不好的,业务对象和框架代码隔离开来,解耦的非常好,但是正因为如此,我们需要在Teacher中自己维护一个Student的列表,于是,我们看到了homeworkListenerList这个变量。

    第二处,在观察者模式中,我们直接调用Observable的notifyObservers来通知被观察者,现在我们只能靠自己了,于是我们看到了这段代码,

    for (HomeworkListener listener : homeworkListenerList) {
        listener.update(event, homework);
    }

    这一点问题也没有,我们继续来看客户端代码吧:

    package com.zuikc.events;

    import java.util.EventListener;

    public class Client {

        public static void main(String[] args) {
            Student student1= new Student("张三");
            Student student2 = new Student("李四");
            Teacher teacher1 = new Teacher("zuikc");
            teacher1.addObserver(student1);
            teacher1.addObserver(student2);
            teacher1.setHomework("事件机制第二天作业");
        }

    }

    结果如下:

    image

    从客户端的角度来说,我们几乎完全没有更改任何地方,跟观察者模式的客户端代码一模一样,但是内部的实现机制上,我们却使用了事件机制。

    现在我们来总结下,观察者模式和事件驱动模型的几个不同点:

    1:事件源不再继承任何模式或者模型本身的父类,彻底将业务代码解耦出来;

    2:在事件模型中,每个监听者(观察者)都需要实现一个自己的接口。没错,看看我们的鼠标事件,分表就有单击、双击、移动等等的事件,这分别就是增加了代码的灵活性;

    不管怎么说,我们用一堆小白代码实现了一个事件驱动模型的样例,虽然没什么实际用处,但也解释了原理,接下来的一节,我们就要看看那些稍微复杂且看上去很有用的代码了!

  • 相关阅读:
    [Spring Unit Testing] Spring Unit Testing with a Java Context
    [Docker] Benefits of Multi-stage Builds
    [Mockito] Mock List interface
    Android自定义垂直滚动自动选择日期控件
    关于 MVC 字段 默认值
    Qt Creator编译时:cannot open file 'debugQtGuiEx.exe' File not found
    ListView开发笔记
    C/C++误区四:char c = getchar();
    ORACLE 中写入txt文本与从Txt文件中读入数据 修改表结构
    wikioi 1214 线段覆盖
  • 原文地址:https://www.cnblogs.com/luminji/p/6947096.html
Copyright © 2020-2023  润新知