Java语言调查者模式先容
副标题#e#
简朴地说,调查者模式界说了一个一对多的依赖干系,让一个或多个调查者工具监察一个主题工具。这样一个主题工具在状态上的变革可以或许通知所有的依赖于此工具的那些调查者工具,使这些调查者工具可以或许自动更新。
调查者模式的布局
调查者(Observer)模式是工具的行为型模式,又叫做颁发-订阅(Publish/Subscribe)模式、模子-视图(Model/View)模式、源-收听者(Source/Listener)模式或从属者(Dependents)模式。
本模式的类图布局如下:
图1、调查者模式的静态布局可从类图中看清楚。
在调查者模式里有如下的脚色:
. 抽象主题(Subject)脚色:主题脚色把所有的调查者工具的引用生存在一个列内外;每个主题都可以有任何数量的调查者。主题提供一个接口可以加上或取消调查者工具;主题脚色又叫做抽象被调查者(Observable)脚色;
图2、抽象主题脚色,有时又叫做抽象被调查者脚色,可以用一个抽象类可能一个接话柄现;在详细的环境下也不解除利用详细类实现。
#p#副标题#e#
. 抽象调查者(Observer)脚色:为所有的详细调查者界说一个接口,在获得通知时更新本身;
图3、抽象调查者脚色,可以用一个抽象类可能一个接话柄现;在详细的环境下也不解除利用详细类实现。
. 详细主题(ConcreteSubject)脚色:生存对详细调查者工具有用的内部状态;在这种内部状态改变时给其调查者发出一个通知;详细主题脚色又叫作详细被调查者脚色;
图4、详细主题脚色,凡是用一个详细子类实现。
.详细调查者(ConcreteObserver)脚色:生存一个指向详细主题工具的引用;和一个与主题的状态相符的状态。详细调查者脚色实现抽象调查者脚色所要求的更新本身的接口,以便使自己的状态与主题的状态自恰。
图5、详细调查者脚色,凡是用一个详细子类实现。
下面给出一个示意性实现的Java代码。首先在这个示意性的实现里,用一个Java接话柄现抽象主题脚色,这就是下面的Subject接口:
public interface Subject
代码清单1、Subject接口的源代码。
{
public void attach(Observer observer);
public void detach(Observer observer);
void notifyObservers();
}
这个抽象主题接口划定出三个子类必需实现的操纵,即 attach() 用来增加一个调查者工具;detach() 用来删除一个调查者工具;和notifyObservers() 用来通知各个调查者刷新它们本身。抽象主题脚色实际上要求子类保持一个以所有的调查者工具为元素的列表。
详细主题则是实现了抽象主题Subject接口的一个详细类,它给出了以上的三个操纵的详细实现。从下面的源代码可以看出,这里给出的Java实现利用了一个Java向量来生存所有的调查者工具,而 attach() 和 detach() 操纵则是对此向量的元素增减操纵。
import java.util.Vector;
代码清单2、ConcreteSubject类的源代码。
import java.util.Enumeration;
public class ConcreteSubject implements Subject
{
public void attach(Observer observer)
{
observersVector.addElement(observer);
}
public void detach(Observer observer)
{
observersVector.removeElement(observer);
}
public void notifyObservers()
{
Enumeration enumeration = observers();
while (enumeration.hasMoreElements())
{
((Observer)enumeration.nextElement()).update();
}
}
public Enumeration observers()
{
return ((Vector) observersVector.clone()).elements();
}
private Vector observersVector = new java.util.Vector();
}
抽象调查者脚色的实现实际上是最为简朴的一个,它是一个Java接口,只声明白一个要领,即update()。这个要领被子类实现后,一被挪用便刷新本身。
public interface Observer
代码清单3、Observer接口的源代码。
{
void update();
}
详细调查者脚色的实现其实只涉及update()要领的实现。这个要领怎么实现与应用密切相关,因此本类只给出一个框架。
public class ConcreteObserver implements Observer
代码清单4、ConcreteObserver类的源代码。
{
public void update()
{
// Write your code here
}
}
#p#分页标题#e#
固然调查者模式的实现要领可以有设计师本身确定,可是因为从AWT1.1开始视窗系统的事件模子回收调查者模式,因此调查者模式在Java语言里的职位较为重要。正因为这个原因,Java语言给出了它本身对换查者模式的支持。因此,本文发起读者在本身的系统中应用调查者模式时,不妨操作Java语言所提供的支持。
Java语言提供的对换查者模式的支持
在Java语言的java.util库内里,提供了一个Observable类以及一个Observer接口,组成Java语言对换查者模式的支持。
Observer接口
这个接口只界说了一个要领,update()。当被调查者工具的状态产生变革时,这个要领就会被挪用。这个要领的实现该当挪用每一个被调查者工具的notifyObservers()要领,从而通知所有的调查工具。
图6、java.util提供的Observer接口的类图。
package java.util;
代码清单5、java.util.Observer接口的源代码。
public interface Observer
{
/**
* 当被调查的工具产生变革时,这个要了解被挪用。
*/
void update(Observable o, Object arg);
}
Observable类
被调查者类都是java.util.Observable类的子类。java.util.Observable提供果真的要领支持调查者工具,这些要领中有两个对Observable的子类很是重要:一个是setChanged(),另一个是notifyObservers()。第一个要领setChanged()被挪用之后会配置一个内部标志变量,代表被调查者工具的状态产生了变革。第二个是notifyObservers(),这个要领被挪用时,会挪用所有挂号过的调查者工具的update()要领,使这些调查者工具可以更新本身。
java.util.Observable类尚有其它的一些重要的要领。好比,调查者工具可以挪用java.util.Observable类的addObserver()要领,将工具一个一个插手到一个列表上。当有变革时,这个列表可以汇报notifyObservers()要领那些调查者工具需要通知。由于这个列表是私有的,因此java.util.Observable的子工具并不知道调查者工具一直在调查着它们。
图7、Java语言提供的被调查者的类图。
被调查者类Observable的源代码:
package java.util;
代码清单6、java.util.Observer接口的源代码。
public class Observable
{
private boolean changed = false;
private Vector obs;
/** 用0个调查者结构一个被调查者。**/
public Observable()
{
obs = new Vector();
}
/**
* 将一个调查者加到调查者列表上面。
*/
public synchronized void addObserver(Observer o)
{
if (!obs.contains(o))
{
obs.addElement(o);
}
}
/**
* 将一个调查者工具从调查者列表上删除。
*/
public synchronized void deleteObserver(Observer o)
{
obs.removeElement(o);
}
/**
* 相当于 notifyObservers(null)
*/
public void notifyObservers()
{
notifyObservers(null);
}
/**
* 假如本工具有变革(当时hasChanged 要了解返回true)
* 挪用本要领通知所有挂号在案的调查者,即挪用它们的update()要领,
* 传入this和arg作为参量。
*/
public void notifyObservers(Object arg)
{
/**
* 姑且存放当前的调查者的状态。拜见备忘录模式。
*/
Object[] arrLocal;
synchronized (this)
{
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 将调查者列表清空
*/
public synchronized void deleteObservers()
{
obs.removeAllElements();
}
/**
* 将“已变革”设为true
*/
protected synchronized void setChanged()
{
changed = true;
}
/**
* 将“已变革”重置为false
*/
protected synchronized void clearChanged()
{
changed = false;
}
/**
* 探测本工具是否已变革
*/
public synchronized boolean hasChanged()
{
return changed;
}
/**
* 返还被调查工具(即此工具)的调查者总数。
*/
public synchronized int countObservers()
{
return obs.size();
}
}
这个Observable类代表一个被调查者工具。一个被调查者工具可以有数个调查者工具,一个调查者可以是一个实现Observer接口的工具。在被调查者工具产生变革时,它会挪用Observable的notifyObservers要领,此要领挪用所有的详细调查者的update()要领,从而使所有的调查者都被通知更新本身。见下面的类图:
图8、利用Java语言提供的对换查者模式的支持。
#p#分页标题#e#
发通知的序次在这里没有指明。Observerable类所提供的缺省实现会凭据Observers工具被挂号的序次通知它们,可是Observerable类的子类可以改掉这一序次。子类并可以在单独的线程里通知调查者工具;可能在一个公用的线程里凭据序次执行。
当一个可调查者工具方才创建时,它的调查者荟萃是空的。两个调查者工具在它们的equals()要领返回true时,被认为是两个相等的工具。
奈何利用Java对换查者模式的支持
为了说明奈何利用Java所提供的对换查者模式的支持,本节给出一个很是简朴的例子。在这个例子里,被调查工具叫做Watched,也就是被监督者;而调查者工具叫做Watcher。—www.bianceng.cn。Watched工具担任自java.util.Obsevable类;而Watcher工具实现了java.util.Observer接口。别的有一个工具Tester,饰演客户端的脚色。
这个简朴的系统的布局如下图所示。
图9、一个利用Observer接口和Observable类的例子。
在客户端改变Watched工具的内部状态时,Watched就会通知Watcher采纳须要的动作。
package com.javapatterns.observer.watching;
import java.util.Observer;
public class Tester
{
static private Watched watched;
static private Observer watcher;
public static void main(String[] args)
{
watched = new Watched();
watcher = new Watcher(watched);
watched.changeData("In C, we create bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Visual Basic, we visualize bugs.");
}
}
代码清单7、Tester类的源代码。
package com.javapatterns.observer.watching;
import java.util.Observable;
public class Watched extends Observable
{
private String data = "";
public String retrieveData()
{
return data;
}
public void changeData(String data)
{
if ( !this.data.equals( data) )
{
this.data = data;
setChanged();
}
notifyObservers();
}
}
代码清单8、Watched类的源代码。
package com.javapatterns.observer.watching;
import java.util.Observable;
import java.util.Observer;
public class Watcher implements Observer
{
public Watcher(Watched w)
{
w.addObserver(this);
}
public void update( Observable ob, Object arg)
{
System.out.println("Data has been changed to: '" + ((Watched)ob).retrieveData() + "'");
}
}
代码清单9、Watcher类的源代码。
可以看出,固然客户端将Watched工具的内部状态赋值了四次,可是值的改变只有三次:
watched.changeData("In C, we create bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Visual Basic, we visualize bugs.");
代码清单10、被调查者的内部状态产生了改变。
对应地,Watcher工具讲述了三次改变,下面就是运行时间措施打印出的信息:
Data has been changed to: 'In C, we create bugs.'
Data has been changed to: 'In Java, we inherit bugs.'
Data has been changed to: 'In Visual Basic, we visualize bugs.'
代码清单11、运行的功效。
菩萨的守瓶龟
想当年齐天大圣为补救师傅唐僧,前往南海普陀山请菩萨降伏魔鬼红孩儿:“菩萨传闻…恨了一声,将手中宝珠净瓶往海心里扑的一掼…只见那海傍边,翻波跳浪,钻出个瓶来,本来是一个怪物驮着出来…要知此怪名和姓,兴风作浪恶乌龟。”
利用面向工具的语言描写,乌龟即是一个调查者工具,它调查的主题是菩萨。一旦菩萨将净瓶掼到海里,就象征着菩萨作为主题挪用了notifyObservers()要领。在西游记中,调查者工具有两个,一个是乌龟,另一个是悟空。悟空的回响在这里临时不思量,而乌龟的回响即是将瓶子驮回海岸。
图10、菩萨和菩萨的守瓶乌龟。
菩萨作为被调查者工具,担任自Observable类;而守瓶乌龟作为调查者,担任自Observer接口;这个模仿系统的实现可以回收Java对换查者模式的支持告竣。
Java中的DEM事件机制
AWT中的DEM机制
#p#分页标题#e#
责任链模式一章中曾谈到,AWT1.0的事件处理惩罚的模子是基于责任链的。这种模子不合用于巨大的系统,因此在AWT1.1版本及今后的各个版本中,事件处理惩罚模子均为基于调查者模式的委派事件模子(Delegation Event Model或DEM)。
在DEM模子内里,主题(Subject)脚色认真宣布(publish)事件,而调查者脚色向特定的主题订阅(subscribe)它所感乐趣的事件。当一个详细主题发生一个事件时,它就会通知所有感乐趣的订阅者。
利用这种宣布-订阅机制的根基设计方针,是提供一种将宣布者与订阅者松散地耦合在一起的接洽形式,以及一种可以或许动态地挂号、打消向一个宣布者的订阅请求的步伐。显然,实现这一构想的能力,是设计抽象接口,并把抽象层和详细层分隔。这在调查者模式里可以清楚地看到。
利用DEM的用词,宣布者叫干事件源(event source),而订阅者叫干事件凝听者(event listener)。在Java内里,事件由类代表,事件的宣布是通过同步地挪用成员要领做到的。
Servlet技能中的的DEM机制
AWT中所利用的DEM事件模子实际上被应用到了所有的Java事件机制上。Servlet技能中的事件处理惩罚机制同样也是利用的DEM模子。
SAX2技能中的DEM机制
DEM事件模子也被应用到了SAX2的事件处理惩罚机制上。
调查者模式的结果
调查者模式的结果有以下的利益:
第一、调查者模式在被调查者和调查者之间成立一个抽象的耦合。被调查者脚色所知道的只是一个详细调查者列表,每一个详细调查者都切合一个抽象调查者的接口。被调查者并不认识任何一个详细调查者,它只知道它们都有一个配合的接口。
由于被调查者和调查者没有细密地耦合在一起,因此它们可以属于差异的抽象化条理。假如被调查者和调查者都被扔到一起,那么这个工具一定超过抽象化和详细化条理。
第二、调查者模式支持广播通讯。被调查者会向所有的挂号过的调查者发出通知,
调查者模式有下面的缺点:
第一、假如一个被调查者工具有许多的直接和间接的调查者的话,将所有的调查者都通知到会耗费许多时间。
第二、假如在被调查者之间有轮回依赖的话,被调查者会触发它们之间举办轮回挪用,导致系统瓦解。在利用调查者模式是要出格留意这一点。
第三、假如对换查者的通知是通过别的的线程举办异步投递的话,系统必需担保投递是以自恰的方法举办的。
第四、固然调查者模式可以随时使调查者知道所调查的工具产生了变革,可是调查者模式没有相应的机制使调查者知道所调查的工具是怎么产生变革的。
调查者模式与其它模式的干系
调查者模式利用了备忘录模式(Memento Pattern)临时将调查者工具存储在被调查者工具内里。
问答题
第一题、我和妹妹跟妈妈说:“妈妈,我和妹妹在院子里玩;饭做好了叫我们一声。”请问这是什么模式?可否给出类图说明?
问答题谜底
第一题谜底、这是调查者模式。我和妹妹让妈妈汇报我们饭做好了,这样我们就可以来用饭了。换用较为技能化的语言来说,当系统的主题(饭)产生变革时,就汇报系统的其它部份(调查者们,也就是妈妈、我和妹妹),使其可以调解内部状态(有开始用饭的筹备),并采纳相应的动作(用饭)。
系统的类图说明如下。
图11、系统的类图。