(六)观察者模式详解(包含观察者模式JDK的漏洞以及事件驱动模型)决了当时的问题,那时LZ接触JAVA刚几个月,比葫芦画瓢的用了观察者模式。

  本章咱们讨论一个除前面的单例以及代理模式以外,一个WEB项目中有可能用到的设计模式,即观察者模式。html

                 提及观察者模式,LZ仍是很是激动的,当初这算是第一个让LZ感觉到设计模式强大的家伙。当初LZ要作一个小型WEB项目,要上传给服务器文件,一个需求就是要显示上传进度,LZ就是用这个模式解决了当时的问题,那时LZ接触JAVA刚几个月,比葫芦画瓢的用了观察者模式。java

                 如今谈及观察者模式,能用到的地方就相对较多了,一般意义上若是一个对象状态的改变须要通知不少对这个对象关注的一系列对象,就可使用观察者模式。web

                 下面LZ先给出观察者模式标准版的定义,引自百度百科。spring

                 定义:观察者模式(有时又被称为发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理全部相依于它的观察者物件,而且在它自己的状态改变时主动发出通知。这一般透过呼叫各观察者所提供的方法来实现。此种模式一般被用来实做事件处理系统。编程

                 上面的定义当中,主要有这样几个意思,首先是有一个目标的物件,通俗点讲就是一个类,它管理了全部依赖于它的观察者物件,或者通俗点说是观察者类,并在它本身状态发生变化时,主动发出通知。设计模式

                 简单点归纳成通俗的话来讲,就是一个类管理着全部依赖于它的观察者类,而且它状态变化时会主动给这些依赖它的类发出通知。数组

                 那么咱们针对上面的描述给出观察者模式的类图,百度百科没有给出观察者模式的类图,这里LZ本身使用工具给各位画一个。tomcat


                 能够看到,咱们的被观察者类Observable只关联了一个Observer的列表,而后在本身状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不须要知道的。 服务器

                上面就将观察者和被观察者两者的耦合度降到很低了,而咱们具体的观察者是必需要知道本身观察的是谁,因此它依赖于被观察者。 并发

                下面LZ给写出一个很简单的观察者模式,来使用JAVA代码简单诠释一下上面的类图。

                首先是观察者接口。

复制代码
package net; //这个接口是为了提供一个统一的观察者作出相应行为的方法 public interface Observer { void update(Observable o); }
复制代码

                再者是具体的观察者。

复制代码
package net; public class ConcreteObserver1 implements Observer{ public void update(Observable o) { System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化"); System.out.println("观察者1作出相应"); } }
复制代码
复制代码
package net; public class ConcreteObserver2 implements Observer{ public void update(Observable o) { System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化"); System.out.println("观察者2作出相应"); } }
复制代码

                下面是被观察者,它有一个观察者的列表,而且有一个通知全部观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面咱们看它的代码。

复制代码
package net; import java.util.ArrayList; import java.util.List; public class Observable { List<Observer> observers = new ArrayList<Observer>(); public void addObserver(Observer o){ observers.add(o); } public void changed(){ System.out.println("我是被观察者,我已经发生变化了"); notifyObservers();//通知观察本身的全部观察者  } public void notifyObservers(){ for (Observer observer : observers) { observer.update(this); } } }
复制代码

                这里面很简单,新增两个方法,一个是为了改变本身的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。

                下面咱们使用客户端调用一下,看一下客户端如何操做。

复制代码
package net; public class Client { public static void main(String[] args) throws Exception { Observable observable = new Observable(); observable.addObserver(new ConcreteObserver1()); observable.addObserver(new ConcreteObserver2()); observable.changed(); } }
复制代码

                 运行结果以下。

 

                 能够看到咱们在操做被观察者时,只要调用changed方法,观察者们就会作出相应的动做,而添加观察者这个行为算是准备阶段,将具体的观察者关联到被观察者上面去。 

                下面LZ给出一个有实际意义的例子,好比咱们常常看的小说网站,都有这样的功能,就是读者能够订阅做者,这当中就有明显的观察者模式案例,就是做者和读者。他们的关系是一旦读者关注了一个做者,那么这个做者一旦有什么新书,就都要通知读者们,这明显是一个观察者模式的案例,因此咱们可使用观察者模式解决。

                 因为JDK中为了方便开发人员,已经写好了现成的观察者接口和被观察者类,下面LZ先给出JDK中现成的观察者和被观察者代码,外加本身的一点解释,来帮助一些读者对JDK中对观察者模式的支持熟悉一下。

                 先来观察者接口。

//观察者接口,每个观察者都必须实现这个接口 public interface Observer { //这个方法是观察者在观察对象产生变化时所作的响应动做,从中传入了观察的对象和一个预留参数 void update(Observable o, Object arg); }

                下面是被观察者类。

复制代码
import java.util.Vector; //被观察者类 public class Observable { //这是一个改变标识,来标记该被观察者有没有改变 private boolean changed = false; //持有一个观察者列表 private Vector obs; public Observable() { obs = new Vector(); } //添加观察者,添加时会去重 public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } //删除观察者 public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } //notifyObservers(Object arg)的重载方法 public void notifyObservers() { notifyObservers(null); } //通知全部观察者,被观察者改变了,你能够执行你的update方法了。 public void notifyObservers(Object arg) { //一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。  Object[] arrLocal; //注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的 //也就是说,在我获取到观察者列表以前,不容许其余线程改变观察者列表 synchronized (this) { //若是没变化直接返回 if (!changed) return; //这里将当前的观察者列表放入临时数组 arrLocal = obs.toArray(); //将改变标识从新置回未改变  clearChanged(); } //注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其余线程能够改变观察者列表 //可是这并不影响咱们当前进行的操做,由于咱们已经将观察者列表复制到临时数组 //在通知时咱们只通知数组中的观察者,当前删除和添加观察者,都不会影响咱们通知的对象 for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } //删除全部观察者 public synchronized void deleteObservers() { obs.removeAllElements(); } //标识被观察者被改变过了 protected synchronized void setChanged() { changed = true; } //标识被观察者没改变 protected synchronized void clearChanged() { changed = false; } //返回被观察者是否改变 public synchronized boolean hasChanged() { return changed; } //返回观察者数量 public synchronized int countObservers() { return obs.size(); } }
复制代码

                 被观察者除了一点同步的地方须要特殊解释一下,其他的相信各位都能看明白各个方法的用途。其实上述JDK的类是有漏洞的,或者说,在咱们使用观察者模式时要注意一个问题,就是notifyObservers这个方法中的这一段代码。

for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);

                 在循环遍历观察者让观察者作出响应时,JDK没有去抓取update方法中的异常,因此假设在这过程当中有一个update方法抛出了异常,那么剩下还未通知的观察者就全都通知不到了,因此LZ我的比较疑惑这样的用意(LZ没法想象JAVA类库的制造者没考虑到这个问题),是sun当时真的忘了考虑这一点,仍是另有它意?固然各位读者若是有本身的看法能够告知LZ,不过LZ认为,无论是sun如此作是别有用意,仍是真的欠考虑,咱们都要注意在update方法里必定要处理好异常,我的以为JDK中比较保险的作法仍是以下这样。

for (int i = arrLocal.length-1; i>=0; i--){ try { ((Observer)arrLocal[i]).update(this, arg); } catch (Throwable e) {e.printStackTrace();} }

                 这样不管其中任何一个update是否成功都不会影响其他的观察者进行更新状态,咱们本身比较保险的作法就是给update方法整个加上try块,或者确认不会发生运行时异常。

 

                 上面LZ和各位一块儿分析了JDK中观察者模式的源码,下面咱们就拿上述小说网的例子,作一个DEMO。

                 首先要搞清楚在读者和做者之间是谁观察谁,很明显,应该是读者观察做者。因此做者是被观察者,读者是观察者,除了这两个类以外,咱们还须要额外添加一个管理器帮咱们管理下做者的列表便于读者关注,因而一个观察者模式的DEMO就出现了。以下,首先是读者类,LZ在各个类都加了点注释。

复制代码
//读者类,要实现观察者接口 public class Reader implements Observer{ private String name; public Reader(String name) { super(); this.name = name; } public String getName() { return name; } //读者能够关注某一位做者,关注则表明把本身加到做者的观察者列表里 public void subscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).addObserver(this); } //读者能够取消关注某一位做者,取消关注则表明把本身从做者的观察者列表里删除 public void unsubscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).deleteObserver(this); } //当关注的做者发表新小说时,会通知读者去看 public void update(Observable o, Object obj) { if (o instanceof Writer) { Writer writer = (Writer) o; System.out.println(name+"知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!"); } } }
复制代码

                       下面是做者类。

复制代码
//做者类,要继承自被观察者类 public class Writer extends Observable{ private String name;//做者的名称 private String lastNovel;//记录做者最新发布的小说 public Writer(String name) { super(); this.name = name; WriterManager.getInstance().add(this); } //做者发布新小说了,要通知全部关注本身的读者 public void addNovel(String novel) { System.out.println(name + "发布了新书《" + novel + "》!"); lastNovel = novel; setChanged(); notifyObservers(); } public String getLastNovel() { return lastNovel; } public String getName() { return name; } }
复制代码

                 而后咱们还须要一个管理器帮咱们管理这些做者。以下。

复制代码
import java.util.HashMap; import java.util.Map; //管理器,保持一份独有的做者列表 public class WriterManager{ private Map<String, Writer> writerMap = new HashMap<String, Writer>(); //添加做者 public void add(Writer writer){ writerMap.put(writer.getName(), writer); } //根据做者姓名获取做者 public Writer getWriter(String name){ return writerMap.get(name); } //单例 private WriterManager(){} public static WriterManager getInstance(){ return WriterManagerInstance.instance; } private static class WriterManagerInstance{ private static WriterManager instance = new WriterManager(); } }
复制代码

                好了,这下咱们的观察者模式就作好了,这个简单的DEMO能够支持读者关注做者,看成者发布新书时,读者会观察到这个事情,会产生相应的动做。下面咱们写个客户端调用一下。

复制代码
//客户端调用 public class Client { public static void main(String[] args) { //假设四个读者,两个做者 Reader r1 = new Reader("谢广坤"); Reader r2 = new Reader("赵四"); Reader r3 = new Reader("七哥"); Reader r4 = new Reader("刘能"); Writer w1 = new Writer("谢大脚"); Writer w2 = new Writer("王小蒙"); //四人关注了谢大脚 r1.subscribe("谢大脚"); r2.subscribe("谢大脚"); r3.subscribe("谢大脚"); r4.subscribe("谢大脚"); //七哥和刘能还关注了王小蒙 r3.subscribe("王小蒙"); r4.subscribe("王小蒙"); //做者发布新书就会通知关注的读者 //谢大脚写了设计模式 w1.addNovel("设计模式"); //王小蒙写了JAVA编程思想 w2.addNovel("JAVA编程思想"); //谢广坤取消关注谢大脚 r1.unsubscribe("谢大脚"); //谢大脚再写书将不会通知谢广坤 w1.addNovel("观察者模式"); } }
复制代码

                    看下咱们获得的结果,就会发现,咱们确实通知了读者它所关注的做者的动态,并且读者取消关注之后,做者的动态将再也不通知该读者。下面是运行结果。

                咱们使用观察者模式的用意是为了做者再也不须要关心他发布新书时都要去通知谁,更重要的是他不须要关心他通知的是读者仍是其它什么人,他只知道这我的是实现了观察者接口的,即咱们的被观察者依赖的只是一个抽象的接口观察者接口,而不关心具体的观察者都有谁都是什么,好比之后要是游客也能够关注做者了,那么只要游客类实现观察者接口,那么同样能够将游客列入到做者的观察者列表中。

                另外,咱们让读者本身来选择本身关注的对象,这至关于被观察者将维护通知对象的职能转化给了观察者,这样作的好处是因为一个被观察者可能有N多观察者,因此让被观察者本身维护这个列表会很艰难,这就像一个老师被许多学生认识,那么是全部的学生都记住老师的名字简单,仍是让老师记住N多学生的名字简单?答案显而易见,让学生们都记住一个老师的名字是最简单的。

                另外,观察者模式分离了观察者和被观察者两者的责任,这样让类之间各自维护本身的功能,专一于本身的功能,会提升系统的可维护性和可重用性。

                观察者模式其实还有另一种形态,就是事件驱动模型,LZ我的以为这两种方式大致上实际上是很是类似的,因此LZ决定一块儿引入事件驱动模型。不过观察者更多的强调的是发布-订阅式的问题处理,而事件驱动则更多的注重于界面与数据模型之间的问题,二者仍是有不少适用场景上的区别的,虽不能一律而论,但放在一块儿讨论仍是很方便各位理解两者。

                说到事件驱动,因为JAVA在桌面应用程序方面有不少欠缺,因此swing的使用其实并非特别普遍,由于你不可能要求大多数人的机子上都安装了JDK,除非你是给特殊用户人群开发的应用程序,这些用户在你的可控范围内,那么swing或许能够派上用场。

                考虑到学习JAVA或者使用JAVA的人群大部分都是在进行web开发,因此本次讨论事件驱动,采用web开发当中所用到的示例。

                相信各位都知道tomcat,这是一个app服务器,在使用的过程当中,或许常常会有人用到listener,即监听器这个概念。那么其实这个就是一个事件驱动模型的应用。好比咱们的spring,咱们在应用启动的时候要初始化咱们的IOC容器,那么咱们的作法就是加入一个listener,这样伴随着tomcat服务器的启动,spring的IOC容器就会跟着启动。

                那么这个listener其实就是事件驱动模型中的监听器,它用来监听它所感兴趣的事,好比咱们springIOC容器启动的监听器,就是实现的ServletContextListener这个接口,说明它对servletContext感兴趣,会监听servletContext的启动和销毁。

                LZ不打算使用这个例子做为讲解,由于它的内部运做比较复杂,须要搬上来tomcat的源码,对于新手来讲,这是个噩耗,因此咱们将上述的例子改成事件驱动来实现。也好让各位针对性的对比观察者模式和事件驱动模型。

                首先事件驱动模型与观察者模式勉强的对应关系能够当作是,被观察者至关于事件源,观察者至关于监听器,事件源会产生事件,监听器监听事件。因此这其中就搀和到四个类,事件源,事件,监听器以及具体的监听器。

                JDK当中依然有现成的一套事件模型类库,其中监听器只是一个标识接口,由于它没有表达对具体对象感兴趣的意思,因此也没法定义监听的事件,只是为了统一,用来给特定的监听器继承。它的源代码以下。

复制代码
package java.util; /** * A tagging interface that all event listener interfaces must extend. * @since JDK1.1 */ public interface EventListener { }
复制代码

                因为代码很短,因此LZ没有删减,当中标注了,全部的事件监听器都必须继承,这是一个标识接口。上述的事件,JDK当中也有一个现成的类供继承,就是EventObject,这个类的源代码以下。

复制代码
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 + "]"; } }
复制代码

             这个类并不复杂,它只是想代表,全部的事件都应该带有一个事件源,大部分状况下,这个事件源就是咱们被监听的对象。

             若是咱们采用事件驱动模型去分析上面的例子,那么做者就是事件源,而读者就是监听器,依据这个思想,咱们把上述例子改一下,首先咱们须要自定义咱们本身的监听器和事件。因此咱们定义以下做者事件。

复制代码
import java.util.EventObject; public class WriterEvent extends EventObject{ private static final long serialVersionUID = 8546459078247503692L; public WriterEvent(Writer writer) { super(writer); } public Writer getWriter(){ return (Writer) super.getSource(); } }
复制代码

              这表明了一个做者事件,这个事件当中通常就是包含一个事件源,在这里就是做者,固然有的时候你可让它带有更多的信息,以方便监听器作出更加细致的动做。下面咱们定义以下监听器。

复制代码
import java.util.EventListener; public interface WriterListener extends EventListener{ void addNovel(WriterEvent writerEvent); }
复制代码

             这个监听器猛地一看,特别像观察者接口,它们承担的功能是相似的,都是提供观察者或者监听者实现本身响应的行为规定,其中addNovel方法表明的是做者发布新书时的响应。加入了这两个类之后,咱们原有的做者和读者类就要发生点变化了,咱们先来看做者类的变化。

复制代码
import java.util.HashSet; import java.util.Set; //做者类 public class Writer{ private String name;//做者的名称 private String lastNovel;//记录做者最新发布的小说 private Set<WriterListener> writerListenerList = new HashSet<WriterListener>();//做者类要包含一个本身监听器的列表 public Writer(String name) { super(); this.name = name; WriterManager.getInstance().add(this); } //做者发布新小说了,要通知全部关注本身的读者 public void addNovel(String novel) { System.out.println(name + "发布了新书《" + novel + "》!"); lastNovel = novel; fireEvent(); } //触发发布新书的事件,通知全部监听这件事的监听器 private void fireEvent(){ WriterEvent writerEvent = new WriterEvent(this); for (WriterListener writerListener : writerListenerList) { writerListener.addNovel(writerEvent); } } //提供给外部注册成为本身的监听器的方法 public void registerListener(WriterListener writerListener){ writerListenerList.add(writerListener); } //提供给外部注销的方法 public void unregisterListener(WriterListener writerListener){ writerListenerList.remove(writerListener); } public String getLastNovel() { return lastNovel; } public String getName() { return name; } }
复制代码

                能够看到,做者类的主要变化是添加了一个本身的监听器列表,咱们使用set是为了它的自然去重效果,而且提供给外部注册和注销的方法,与观察者模式相比,这个功能自己是由基类Observable提供的,不过观察者模式中有统一的观察者Observer接口,可是监听器没有,虽然说有EventListener这个超级接口,但它毕竟没有任何行为。因此咱们通常须要维持一个本身特有的监听器列表。

                下面咱们看读者类的变化,以下。

复制代码
public class Reader implements WriterListener{ private String name; public Reader(String name) { super(); this.name = name; } public String getName() { return name; } //读者能够关注某一位做者,关注则表明把本身加到做者的监听器列表里 public void subscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).registerListener(this); } //读者能够取消关注某一位做者,取消关注则表明把本身从做者的监听器列表里注销 public void unsubscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).unregisterListener(this); } public void addNovel(WriterEvent writerEvent) { Writer writer = writerEvent.getWriter(); System.out.println(name+"知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!"); } }
复制代码

               读者类的变化,首先原本是实现Observer接口,如今要实现WriterListener接口,响应的update方法就改成咱们定义的addNovel方法,当中的响应基本没变。另外就是关注和取消关注的方法中,原来是给做者类添加观察者和删除观察者,如今是注册监听器和注销监听器,几乎是没什么变化的。

               咱们完全将刚才的观察者模式改为了事件驱动,如今咱们使用事件驱动的类再运行一下客户端,其中客户端代码和WriterManager类的代码是彻底不须要改动的,直接运行客户端便可。咱们会发现获得的结果与观察者模式如出一辙。

               走到这里咱们发现两者能够达到的效果如出一辙,那么二者是否是同样呢?

               答案固然是否认的,首先咱们从实现方式上就能看出,事件驱动能够解决观察者模式的问题,但反过来则不必定,另外两者所表达的业务场景也不同,好比上述例子,使用观察者模式更贴近业务场景的描述,而使用事件驱动,从业务上讲,则有点勉强。

               两者除了业务场景的区别之外,在功能上主要有如下区别。

               1,观察者模式中观察者的响应理论上讲针对特定的被观察者是惟一的(说理论上惟一的缘由是,若是你愿意,你彻底能够在update方法里添加一系列的elseif去产生不一样的响应,但LZ早就说过,你应该忘掉elseif),而事件驱动则不是,由于咱们能够定义本身感兴趣的事情,好比刚才,咱们能够监听做者发布新书,咱们还能够在监听器接口中定义其它的行为。再好比tomcat中,咱们能够监听servletcontext的init动做,也能够监听它的destroy动做。

               2,虽然事件驱动模型更加灵活,但也是付出了系统的复杂性做为代价的,由于咱们要为每个事件源定制一个监听器以及事件,这会增长系统的负担,各位看看tomcat中有多少个监听器和事件类就知道了。

               3,另外观察者模式要求被观察者继承Observable类,这就意味着若是被观察者原来有父类的话,就须要本身实现被观察者的功能,固然,这一尴尬事情,咱们可使用适配器模式弥补,但也不可避免的形成了观察者模式的局限性。事件驱动中事件源则不须要,由于事件源所维护的监听器列表是给本身定制的,因此没法去制做一个通用的父类去完成这个工做。

               4,被观察者传送给观察者的信息是模糊的,好比update中第二个参数,类型是Object,这须要观察者和被观察者之间有约定才可使用这个参数。而在事件驱动模型中,这些信息是被封装在Event当中的,能够更清楚的告诉监听器,每一个信息都是表明的什么。

               因为上述使用事件驱动有点勉强,因此LZ给各位模拟一个咱们js当中的一个事件驱动模型,就是按钮的点击事件。

               在这个模型当中,按钮天然就是事件源,而事件的种类有不少,好比点击(click),双击(dblclick),鼠标移动事件(mousemove)。咱们的监听器与事件个数是同样的,因此这也是事件驱动的弊端,咱们须要一堆事件和监听器,下面LZ一次性给出这三种事件和监听器,其他还有不少事件,相似,LZ这里省略。

复制代码
import java.util.EventObject; //按钮事件基类 public abstract class ButtonEvent extends EventObject{ public ButtonEvent(Object source) { super(source); } public Button getButton(){ return (Button) super.getSource(); } } //点击事件 class ClickEvent extends ButtonEvent{ public ClickEvent(Object source) { super(source); } } //双击事件 class DblClickEvent extends ButtonEvent{ public DblClickEvent(Object source) { super(source); } } //鼠标移动事件 class MouseMoveEvent extends ButtonEvent{ //鼠标移动事件比较特殊,由于它须要告诉监听器鼠标当前的坐标是在哪,咱们记录为x,y private int x; private int y; public MouseMoveEvent(Object source, int x, int y) { super(source); this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } }
复制代码

                     以上是三种事件,都很是简单,只有鼠标移动须要额外的坐标,下面给出三种监听器。

复制代码
import java.util.EventListener; //点击监听器 interface ClickListener extends EventListener{ void click(ClickEvent clickEvent); } //双击监听器 interface DblClickListener extends EventListener{ void dblClick(DblClickEvent dblClickEvent); } //鼠标移动监听器 interface MouseMoveListener extends EventListener{ void mouseMove(MouseMoveEvent mouseMoveEvent); }
复制代码

                    三种监听器分别监听点击,双击和鼠标移动。下面给出咱们最重要的类,Button。

复制代码
//咱们模拟一个html页面的button元素,LZ只添加个别属性,其他属性同理 public class Button { private String id;//这至关于id属性 private String value;//这至关于value属性 private ClickListener onclick;//咱们彻底模拟原有的模型,这个其实至关于onclick属性 private DblClickListener onDblClick;//同理,这个至关于双击属性 private MouseMoveListener onMouseMove;//同理 //按钮的单击行为 public void click(){ onclick.click(new ClickEvent(this)); } //按钮的双击行为 public void dblClick(){ onDblClick.dblClick(new DblClickEvent(this)); } //按钮的鼠标移动行为 public void mouseMove(int x,int y){ onMouseMove.mouseMove(new MouseMoveEvent(this,x,y)); } //至关于给id赋值 public void setId(String id) { this.id = id; } //相似 public void setValue(String value) { this.value = value; } //这个至关于咱们在给onclick添加函数,即设置onclick属性 public void setOnclick(ClickListener onclick) { this.onclick = onclick; } //同理 public void setOnDblClick(DblClickListener onDblClick) { this.onDblClick = onDblClick; } //同理 public void setOnMouseMove(MouseMoveListener onMouseMove) { this.onMouseMove = onMouseMove; } //如下get方法 public String getId() { return id; } public String getValue() { return value; } public ClickListener getOnclick() { return onclick; } public DblClickListener getOnDblClick() { return onDblClick; } public MouseMoveListener getOnMouseMove() { return onMouseMove; } }
复制代码

                     能够看到,按钮Button类有不少属性,都是咱们常常看到的,id,value,onclick等等。下面咱们模拟编写一个页面,这个页面能够当作是一个JSP页面,咱们只有一个按钮,咱们用JAVA语言把它描述出来,以下。

复制代码
//假设这个是咱们写的某一个特定的jsp页面,里面可能有不少元素,input,form,table,等等 //咱们假设只有一个按钮 public class ButtonJsp { private Button button; public ButtonJsp() { super(); button = new Button();//这个能够当作咱们在页面写了一个button元素 button.setId("submitButton");//取submitButton为id button.setValue("提交");//提交按钮 button.setOnclick(new ClickListener() {//咱们给按钮注册点击监听器 //按钮被点,咱们就验证后提交 public void click(ClickEvent clickEvent) { System.out.println("--------单击事件代码---------"); System.out.println("if('表单合法'){"); System.out.println("\t表单提交"); System.out.println("}else{"); System.out.println("\treturn false"); System.out.println("}"); } }); button.setOnDblClick(new DblClickListener() { //双击的话咱们提示用户不能双击“提交”按钮 public void dblClick(DblClickEvent dblClickEvent) { System.out.println("--------双击事件代码---------"); System.out.println("alert('您不能双击"+dblClickEvent.getButton().getValue()+"按钮')"); } }); button.setOnMouseMove(new MouseMoveListener() { //这个咱们只简单提示用户鼠标当前位置,示例中加入这个事件 //目的只是为了说明事件驱动中,能够包含一些特有的信息,好比坐标 public void mouseMove(MouseMoveEvent mouseMoveEvent) { System.out.println("--------鼠标移动代码---------"); System.out.println("alert('您当前鼠标的位置,x坐标为:"+mouseMoveEvent.getX()+",y坐标为:"+mouseMoveEvent.getY()+"')"); } }); } public Button getButton() { return button; } }
复制代码

                  以上能够认为咱们给web服务中写了一个简单的页面,下面咱们看客户在访问咱们的页面时,咱们的页面在作什么。

复制代码
public class Client { public static void main(String[] args) { ButtonJsp jsp = new ButtonJsp();//客户访问了咱们的这个JSP页面 //如下客户开始在按钮上操做 jsp.getButton().dblClick();//双击按钮 jsp.getButton().mouseMove(10, 100);//移动到10,100 jsp.getButton().mouseMove(15, 90);//又移动到15,90 jsp.getButton().click();//接着客户点了提交  } }
复制代码

                咱们看运行结果能够看到,咱们的三个事件都起了做用,最终提交了表单。



                以上就是模拟整个JSP页面中,咱们的按钮响应用户事件的过程,我相信经过这两个例子,各位应该对观察者模式和事件驱动都有了本身的理解和认识,两者都是用来处理变化与响应的问题,其中观察者更多的是发布-订阅,也就是相似读者和做者的关系,而事件驱动更多的是为了响应客户的请求,从而制定一系列的事件和监听器,去处理客户的请求与操做。

               两者其实都是有本身的弱项的,只有掌握了模式的弱项才能更好的使用,不是有句话叫“真正了解一个东西,不是知道它能干什么,而是知道它不能干什么。”吗?

               观察者模式所欠缺的是设计上的问题,即观察者和被观察者是多对一的关系,那么反过来的话,就没法支持了。

               各位能够尝试将两者位置互换达到这个效果,这算是设计模式的活用,很简单,就是让被观察者作成一个接口,提供是否改变的方法,让观察者维护一个被观察者的列表,另外开启一个线程去不断的测试各个被观察者是否改变。因为本篇已经够长,因此LZ再也不详细编写,若是有哪位读者有须要,能够在下方留言,LZ看到的话,若是有时间,会写出来放到资源里供各位下载。

               观察者模式还有一个缺点就是,每个观察者都要实现观察者接口,才能添加到被观察者的列表当中,假设一个观察者已经存在,并且咱们没法改变其代码,那么就没法让它成为一个观察者了,不过这个咱们依然可使用适配器模式解决。可是还有一个问题就很差解决了,就是假如咱们不少类都是现成的,当被观察者发生变化时,每个观察者都须要调用不一样的方法,那么观察者模式就有点捉襟见肘的感受了,咱们必须适配每个类去统一他们变化的方法名称为update,这是一个很可怕的事情。

               对于事件驱动就没有这样的问题,咱们能够实现多个监听器来达到监听多个事件源的目的,可是它的缺点刚才已经说过了,在事件源或者事件增长时,监听器和事件类一般状况下会成对增长,形成系统的复杂性增长,不过目前看来,事件驱动模型通常都比较稳定,因此这个问题并不太明显,由于不多见到无限增长事件的状况发生。

               还有一个缺点就是咱们的事件源须要看准时机触发本身的各个监听器,这也从某种意义上增长了事件源的负担,形成了类必定程度上的臃肿。

               最后,LZ再总结下两者针对的业务场景概述。

               观察者模式:发布(release)--订阅(subscibe),变化(change)--更新(update)

               事件驱动模型:请求(request)--响应(response),事件发生(occur)--事件处理(handle)       
               感谢各位的收看。

               下期预告,策略模式。

相关文章
相关标签/搜索