第一节中的示例过于简单,简单到让大家觉得这样的代码简直毫无用处。但是没办法,我们要继续写这毫无用处的代码,然后引出下一阶段真正有益的代码。
一:事件驱动模型初窥
我们要说事件驱动模型是观察者模式的升级版本,那我们就要说说其中的对应关系:
观察者对应监听器(学生)
被观察者对应事件源(教师)
事件源产生事件,事件带有事件源,监听器监听事件。爱钻牛角尖的朋友可能会说,我擦,什么叫产生事件,监听事件,事件事件到底什么?
莫慌,如果我们用代码来说事,事件它就是个类,事件源也是个类。这里面一共牵扯到四个类,事件源(即教师、即被观察者)、事件(是一个类,见下文,一般我们以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的设计者们进行了最高级的抽象,就是让上层类只是代表了:我是一个事件(含有事件源),或,我是一个监听者!
二:老师布置作业的事件驱动模型版本
老规矩,让我们先给出类图:
然后,代码实现之:
观察者接口(学生)。由于在事件驱动模型中,只有一个没有任何方法的接口,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》 \n", 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 \n", 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("事件机制第二天作业");
}
}
结果如下:
从客户端的角度来说,我们几乎完全没有更改任何地方,跟观察者模式的客户端代码一模一样,但是内部的实现机制上,我们却使用了事件机制。
现在我们来总结下,观察者模式和事件驱动模型的几个不同点:
1:事件源不再继承任何模式或者模型本身的父类,彻底将业务代码解耦出来;
2:在事件模型中,每个监听者(观察者)都需要实现一个自己的接口。没错,看看我们的鼠标事件,分表就有单击、双击、移动等等的事件,这分别就是增加了代码的灵活性;
不管怎么说,我们用一堆小白代码实现了一个事件驱动模型的样例,虽然没什么实际用处,但也解释了原理,接下来的一节,我们就要看看那些稍微复杂且看上去很有用的代码了!