概念
观察者模式又叫发布-订阅模式(publish-subscribe)。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个 主题对象在状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己。
结构分析
Subject(被观察对象接口):
规定ConcreteSubject的同意接口。
每个Subject可以有多个Observer。
ConcreteSubject(具体被观察对象):
维护对所有具体观察者的引用的列表。
状态发生改变时会发送通知给所有注册的观察者。
Observer(观察者接口):
规定ConcreteObserver的统一接口。
定义了一个update()方法,在被观察对象状态改变时会被调用。
ConcreteObserver(具体观察者):
维护一个队ConcreteSubject的引用。
特定状态与ConcreteSubject同步。
实现Observer 接口,通过update()方法接收ConcreteSubject通知 。
理论的有点虚,下面给出一个示意性实现的Java代码,大家感受一下。
示例代码
Subject抽象主题接口:
package observerpattern;
public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
具体主题ConcreteSubject:
package observerpattern;
import java.util.Enumeration;
import java.util.Vector;
public class ConcreteSubject implements Subject {
// 用一向量vector来维持观察者队列
private Vector observersVector = new Vector();
@Override
public void registerObserver(Observer observer) {
// TODO Auto-generated method stub
observersVector.addElement(observer);
}
@Override
public void removeObserver(Observer observer) {
// TODO Auto-generated method stub
observersVector.removeElement(observer);
}
@Override
public void notifyObservers() {
// TODO Auto-generated method stub
System.out.println("主题有所改变,通知观察者");
// 复制一份观察者列表,这样可以避免线程安全问题
Enumeration enumeration = ((Vector) observersVector.clone()).elements();
// 逐个通知观察者
while (enumeration.hasMoreElements()) {
((Observer) enumeration.nextElement()).update(this, null);
}
}
}
观察者接口Observer:
package observerpattern;
public interface Observer {
public void update(Subject subject, Object arg);
}
具体观察者ConcreteObserver:
package observerpattern;
public class ConcreteObserver implements Observer {
@Override
public void update(Subject subject, Object arg) {
// TODO Auto-generated method stub
System.out.println("收到主题改变的通知");
}
}
测试Test:
package observerpattern;
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Observer watcher = new ConcreteObserver(); // 建立观察者
Subject watched = new ConcreteSubject(); // 被观察对象,主题
watched.registerObserver(watcher); // 被观察对象添加观察者
watched.notifyObservers(); // 当被观察对象改变时,通知观察者
}
}
输出:
主题有所改变,通知观察者
收到主题改变的通知
Java对观察者模式的支持
虽然观察者模式的实现方法可以有设计师自己确定,但是因为从AWT1.1开始视窗系统的事件模型采用观察者模式,因此观察者模式在Java语言里的地位较为重要。正因为这个原因,Java语言给出了它自己对观察者模式的支持。因此,本文建议读者在自己的系统中应用观察者模式时,不妨利用Java语言所提供的支持。
在Java语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成Java语言对观察者模式的支持。
为了使读者透彻理解java观察者模式,这里加入了对以上两个类的源码解析~
Observable类:
此类表示模型视图范例中的 observable 对象,继承它的类表示应用程序想要观察的对象。一个 observable 对象可以有一个或多个观察者。观察者是实现Observer接口的任意对象。一个 observable 实例改变后,调用 Observable 的 notifyObservers 方法的应用程序会通过调用观察者的 update 方法来通知观察者该实例发生了改变。
Observable类中使用了一个java.util.Vector对象用来维持注册的观察者队列,并利用一个布尔变量判断当前对象是否发生状态变化:
private boolean changed = false;
private Vector obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector();
}
当主题对象状态发生改变时布尔变量changed置为true。
protected synchronized void setChanged() {
changed = true;
}
当有观察者需要监视主体对象,或者主题对象不再被某个观察者监视时,以下两个函数分别用于添加观察者和删除观察者:
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);
}
synchronized关键字表示这两个函数是线程同步的~
如果主题对象(本对象)状态发生了改变,就会通知vector队列中所维持的所有观察者~arg为所有传递的参数
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
注意这里同样需要 同步,并且是按照观察者注册顺序的反序通知观察者的~
Observer接口:
这是个接口类,这个接口只有一个为实现的抽象方法update。实现该接口的对象成为观察者,该对象要实现update方法。注册了该对象(观察者)的对象(观察者)实例条用notifiyObservers方法后,观察者会自动执行update方法。
源码:
package java.util;
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
代码示例
该实例模拟了烧水的过程,主要涉及三个对象,Heater(热水器),Display(显示器),Alarm(报警器),又添加了个Alarm2报警器主要是为了验证主题对象发送通知的顺序是注册的反序,读者可以通过看上面的源码、示例代码及运行结果确认这一点。
模拟过程:为了便于运行,水的初始化温度为90,沸点为95,显示器依据热水器显示温度,显示器显示温度为95时,报警器开始报警。明显可以看出Heater是subject ,Display 是它的 Obsrver,同时Display亦是subject,因为它要被报警器观察,所以Alarm是Display的Observer.
主题对象Heater类:package observerpattern;
import java.util.Observable;
public class Heater extends Observable {
private int temperature;
/**
* @return the temperature
*/
public int getTemperature() {
return temperature;
}
/**
* @param temperature
* the temperature to set
*/
public void setTemperature(int temperature) {
this.temperature = temperature;
}
public void boilWater() {
for (int i = 90; i < 110; i++) {
temperature = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setChanged();
this.notifyObservers();
}
}
}
既是观察者又是主题对象的显示器Display类:
package observerpattern;
import java.util.Observable;
import java.util.Observer;
public class Display extends Observable implements Observer {
private String statu = "未开";
@Override
public void update(Observable o, Object arg) {
// TODO Auto-generated method stub
displayTemperature(((Heater) o).getTemperature());
}
/**
* @return the status
*/
public String getStatu() {
return statu;
}
/**
* @param statu
* the statu to set
*/
public void setStatu(String statu) {
this.statu = statu;
}
private void displayTemperature(int temperature) {
if (temperature > 100) {
this.setStatu("沸腾");
this.setChanged();
this.notifyObservers(temperature);
}
System.out.println("状态: " + statu + " 现在温度: " + temperature);
}
}
观察者Alarm及Alarm2:
package observerpattern;
import java.util.Observable;
import java.util.Observer;
public class Alarm implements Observer {
@Override
public void update(Observable o, Object arg) {
// TODO Auto-generated method stub
makeAlarm((int) arg);
}
private void makeAlarm(int temperature) {
System.out.println("嘀嘀嘀。。。水已经开了,现在水温是 " + temperature);
}
}
package observerpattern;
import java.util.Observable;
import java.util.Observer;
public class Alarm2 implements Observer{
@Override
public void update(Observable o, Object arg) {
// TODO Auto-generated method stub
System.out.println(this.getClass().getName() + "显示:水已经开了,现在水温是 " + (int)arg);
}
}
测试类:
package observerpattern;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Heater heater = new Heater();
Display display = new Display();
heater.addObserver(display);
Alarm alarm = new Alarm();
Alarm2 alarm2 = new Alarm2();
display.addObserver(alarm);
display.addObserver(alarm2);
heater.boilWater();
}
}
输出:
状态: 未开 现在温度: 90
状态: 未开 现在温度: 91
状态: 未开 现在温度: 92
状态: 未开 现在温度: 93
状态: 未开 现在温度: 94
状态: 未开 现在温度: 95
状态: 未开 现在温度: 96
状态: 未开 现在温度: 97
状态: 未开 现在温度: 98
状态: 未开 现在温度: 99
状态: 未开 现在温度: 100
observerpattern.Alarm2显示:水已经开了,现在水温是 101
嘀嘀嘀。。。水已经开了,现在水温是 101
状态: 沸腾 现在温度: 101
observerpattern.Alarm2显示:水已经开了,现在水温是 102
嘀嘀嘀。。。水已经开了,现在水温是 102
状态: 沸腾 现在温度: 102
observerpattern.Alarm2显示:水已经开了,现在水温是 103
嘀嘀嘀。。。水已经开了,现在水温是 103
状态: 沸腾 现在温度: 103
observerpattern.Alarm2显示:水已经开了,现在水温是 104
嘀嘀嘀。。。水已经开了,现在水温是 104
状态: 沸腾 现在温度: 104
observerpattern.Alarm2显示:水已经开了,现在水温是 105
嘀嘀嘀。。。水已经开了,现在水温是 105
状态: 沸腾 现在温度: 105
observerpattern.Alarm2显示:水已经开了,现在水温是 106
嘀嘀嘀。。。水已经开了,现在水温是 106
状态: 沸腾 现在温度: 106
observerpattern.Alarm2显示:水已经开了,现在水温是 107
嘀嘀嘀。。。水已经开了,现在水温是 107
状态: 沸腾 现在温度: 107
observerpattern.Alarm2显示:水已经开了,现在水温是 108
嘀嘀嘀。。。水已经开了,现在水温是 108
状态: 沸腾 现在温度: 108
observerpattern.Alarm2显示:水已经开了,现在水温是 109
嘀嘀嘀。。。水已经开了,现在水温是 109
状态: 沸腾 现在温度: 109
优点:
- 支持松耦合和减少依赖性。客户端不再依赖于观察器,因为通过使用主体和 Observer 接口对客户端进行了隔离。许多框架具有此优点,在这些框架中的应用程序组件可以注册为当(低级)框架事件发生时得到通知。结果,框架将调用应用程序组件,但不会依赖于它。
- 观察器数目可变。观察器可以在运行时附加和分离,因为主体对于观察器数目没有任何假定。此功能在这样的情况下是很有用的:观察器数在设计时是未知的。例如,如果用户在应用程序中打开的每个窗口都需要一个观察器。
缺点:
- 性能降低。在许多实现中,观察器的 update() 方法可能与主体在同一线程中执行。如果观察器列表很长,则执行 Notify() 方法可能需要很长时间。抽取对象依赖性并不意味着添加观察器对应用程序没有任何影响。
- 内存泄漏。在 Observer 中使用的回调机制(当对象注册为以后调用时)会产生一个常见的错误,从而导致内存泄漏,甚至是在托管的 C# 代码中。假定观察器超出作用范围,但忘记取消对主体的订阅,那么主体仍然保留对观察器的引用。此引用防止垃圾收集在主体对象也被破坏之前重新分配与观察器关联的内存。如果观察器的生存期比主体的生存期短得多(通常是这种情况),则会导致严重的内存泄漏。
- 隐藏的依赖项。观察器的使用将显式依赖性(通过方法调用)转变为隐式依赖性(通过观察器)。如果在整个应用程序中广泛地使用观察器,则开发人员几乎不可能通过查看源代码来了解所发生的事情。这样,就使得了解代码更改的含意非常困难。此问题随传播级别急剧增大(例如,充当 Subject 的观察器)。因此,应该仅在少数定义良好的交互(如 Model-View-Controller 模式中模型和视图之间的交互)中使用观察器。最好不要在域对象之间使用观察器。
- 测试 / 调试困难。尽管松耦合是一项重大的体系结构功能,但是它可以使开发更困难。将两个对象去耦的情况越多,在查看源代码或类的关系图时了解它们之间的依赖性就越难因此,仅当可以安全地忽略两个对象之间的关联时才应该将它们松耦合(例如,如果观察器没有副作用)。