模式定义
我们知道面向对象的一个主题就是类实例的实体化,即每个实体都有自己的状态和行为,而仿照真实世界的不同客观实体之间是存在诸多联系的,则一个很常见的对象联动方式就是反馈行为的发生。最常见的场景如:班车进站,众旅客上车,这是一个典型的对象反馈行为。由此产生的一种描述该现象的设计模式,称之为观察者模式,也有其他的名字如发布-订阅模式、模型-视图模式等。
观察者模式作为一种对象行为型模式,用于监控对象状态,并根据对象状态的改变而通知与其存在依赖关系的其他对象,所谓的“通知”其实就是产生反馈效果,所以该模式通常应用于一对一和一对多的对象关系中。
模式示例
以上面提到的班车进站和乘客上车为例,结构如下:
1、Observer作为观察者接口,只有一个update方法,作为抽象的响应函数,重点是其参数
2、passenger作为具体观察者,即等待车辆的乘客,实现update函数,即上车动作,这里的参数是观察目标,即车辆的编号(标志车辆)
3、Observable作为观察目标类,包含一个对观察者的集合,以实现在自身状态发生改变时,完成“通知”功能
4、Bus继承Observable类,也就是具体的目标类,这里模仿的是汽车进站场景,所以自身提供了一个进站函数(覆盖空的父类change函数),如果是汽车的其他场景,则需要提供其他类型的函数,作为改变状态,产生反馈源信息的条件。
示例代码
public class t{
public static void main(String[] args){
Observer pass1=new passenger("张三");//构造三个观察者
Observer pass2=new passenger("李四");
Observer pass3=new passenger("王五");
Observable car=new Bus("916-1号线");//一个观察目标
car.add(pass1);//添加观察关系
car.add(pass2);
car.add(pass3);
car.change();//观察目标更新状态
}
}
interface Observer{//抽象观察者类
void update(String id);
}
class passenger implements Observer{//具体观察者类
private String name=null;
public passenger(String name){
this.name=name;
}
public void update(String id){
System.out.println(name+" get in the car NO:"+id);
}
}
class Observable{//观察目标类
private List<Observer> observers=null;//观察者的属性集合
protected String id=null;
public Observable(String id){
this.id=id;
this.observers=new ArrayList<Observer>();
}
public void add(Observer o){
this.observers.add(o);
}
public void delete(Observer o){
this.observers.remove(o);
}
public void Notify(){//发出更新通知
for(int i=0;i<observers.size();i++){
observers.get(i).update(this.id);
}
}
public void change(){}//待覆盖的更新状态事件
}
class Bus extends Observable{//具体目标类
public Bus(String id){
super(id);
}
public void change(){//更新事件
System.out.println("列车进站。。。");
Notify();
}
}
此处只是演示了目标对象的更新事件,对观察者的通知机制,并没有在目标对象中包含具体的状态,也没有进行对并发的处理。
更新函数参数
1、关于示例中的update函数的参数,在此例中观察目标并没有包含具体的状态,所以在通知观察者的函数中并没有提供自身引用作为参数,简单说就是,观察者对于观察目标的更新,只需要根据自身做出更新即可,不需要借助观察目标的状态。
2、在一些特殊的或者极为简单的场景中这是可以的,但是在简单如上例中所说,乘客等车这样单调的情形中,仍然需要传递一个车辆id作为参数(多个车辆中标志乘客等待的是某一个),所以通常的update函数是需要提供参数的(下面有Java API参考)。例如在Java的事件处理中,需要有事件源(观察目标)、事件处理程序(观察者),还要有事件对象本身(需要传递的对象),这里的事件处理程序需要针对事件对象做出操作,即update中需要提供事件对象作为参数。
3、更为一般的情况则是,事件处理程序既需要事件对象,也需要事件源的状态,然后才能做出处理,即需要同时提供观察目标的状态和发生的事件对象,即update中需要包含两个参数。
参考源代码
JDK中提供有对观察者模式的支持,在java.util包中提供有Observer接口和Observable类
观察者接口
public interface Observer {
/*
当观察目标状态更新时,观察者的更新函数
包括两个参数,一个是观察目标,用以提供观察目标的状态
另一个则是事件对象,如果只需要根据目标做出更新,则arg为null
*/
void update(Observable o, Object arg);
}
观察目标类
public class Observable {
private boolean changed = false;//状态更新的标志
private Vector obs;//用以保存观察者的集合
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector();
}
/*
在添加观察者的函数中可以看出,即便vector作为所谓的线程安全集合
自带的同步机制也只是实现addElement方法是同步方法,并不能保证addObserver的同步
所以addObserver操作仍然需要synchronized修饰
在去除重复观察者方面,利用vector的contains方法,而不是set集合,原因猜测
1.对观察者模式的支持提出的时间比较早,跟vector一样古老,所以用的是vector,
至于没有更改估计跟vector一直用Enumeration至死都没有改用Iterator一样,
太古老,所以直接放弃不用了。没有考证,只是猜测
2.线程安全集合,虽然此处没看出在同步方法之上再覆盖一层同步方法有何益处(
除otifyObservers之外,所有方法都是synchronized修饰的同步方法,且notifyObservers
函数中是复制出新集合,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);
}
/*
如果只需要传递观察目标接口,则第二个参数为null
甚至专门提供了一个无参的notifyObservers方法来实现
*/
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
/*
这段代码的作用是在观察目标状态更新后,将原来的观察者集合复制出来为allLocal
这样可以保证在通知所有观察者的过程中,原来的集合仍然可以同时添加观察者或者
删除观察者,类似于一种读写分离或者写时复制的思想(此时是在通知观察者过程中,
原有vector可以存在其他行为,即复制完成后。复制过程是全程加锁的,vector的添加、
删除观察者操作也是加锁的,所以复制过程是同步的)
并发中存在情况有
1.在复制过程之后,通知操作完成之前,此时新添加的观察者不会收到更新通知
2.在复制过程之后,通知操作完成之前,此时新删除的观察者也会收到更新通知
源码说明如下:
*/
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);
}
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();
}
}
总结
观察者模式适用于对象的状态更新会产生对其他对象的影响,即需要其他对象针对该对象的更新操作作出自己的更新/调整操作。较好的类之间的耦合情况应该是,一个类实例的更新只对一个或很少的其他实例产生影响。
参考设计模式开闭原则,关于update函数的参数问题,如果无参数则具体目标类与具体观察者类无依赖关系,只是针对抽象类编程,满足开闭原则;如果需要以具体目标类作为参数,则如果需要的状态为抽象目标类的共有状态,则仍可以满足开闭原则,面向抽象编程,如果需要提供的目标类状态信息为具体目标类私有,则此时观察者类面向具体目标类编程,违反开闭原则,此种情形下抽象目标类的作用仅限于复用,不提供抽象编程的帮助。