观察者模式
一、现实生活中的实例
早上起床,推开窗,看到漫天雪花,我们知道天气降温并下着大雪,于是我们穿上棉服打着雨伞出门;在过马路的时候,看到了红灯,
于是我们停下脚步等着绿灯;当穿过马路,走到公交站台看到公交车刚刚走远,于是我们摆手拦下一辆出租车坐上去上班。
从天气变化来选择出门的穿着,从交通灯的颜色变化来选择停留,从公交车远走来重新选择车子,我们总是观察者事物的变化来改变
我们的动作。
二、软件设计
从生活中的启发,我们把通过观察事物的变化来改变另一事物的行为演变成为了一种新的设计模式—观察者模式。
观察者模式的模式动机是建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。
在此,发生改变的对象称为观察目标,而被通知的对象称为观察者。
从实例中思考也可知道,多个观察者可以观察同一个对象,而且这些观察者之间没有相互联系。比如,开车的人、汽车的人、走路的
人都在看交通灯,并且都各自根据交通灯的变化而进行各自的动作。
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
观察者模式,是一种对象行为型模式;一个观察对象的变化,其相关依赖的N个观察者都会被通知并发生相应的变化。
三、Java中的观察者模式
Java是面向对象的编程,Java在java.util中提供了Observer接口和Observable类。
3.1、Observer
/**
* A class can implement the Observer
interface when it
* wants to be informed of changes in observable objects.
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an Observable object's
* notifyObservers
method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the notifyObservers
* method.
*/
void update(Observable o, Object arg);
}
该接口约定了观察者的行为,所有观察者需要在被观察目标发生变化时做出相应的响应,具体的就是实现Observer接口的update()方法。
使用观察者模式,需要有调用者(主要应该是具体的业务类)、观察者、观察目标。
当前的Observer接口是观察者,观察者要观察观察的目标,目标变化时调用更新方法,因此update()方法参数需要传入具体的观察目标Observable的对象引用。
调用者如何传递数据给观察者呢?当然是调用观察目标的notifyObservers(Object arg)方法传入数据,把数据再通过观察者的update(Observable o, Object arg)的
方法把数据传递给观察者,这样就实现了调用者与观察者的通信。换句话说:就是通过Observer的update(Observable o, Object arg)方法,把调用者、观察目标、观察者进行关联和通信。
3.1、Observable
此类表示可观察对象或模型视图范例中的“数据”。 它可以被子类化以表示应用程序想要观察到的对象。
可观察对象可以有一个或多个观察者。 观察者可以是实现接口Observer的任何对象。 在可观察到的实例发生变化之后,调用Observable的notifyObservers方法的应用程序会使其所有观察者通过调用其update方法通知更改。
通知的发送顺序是未指定的。 Observable类中提供的默认实现将以注册感兴趣的顺序通知Observers,但子类可能会更改此顺序,不使用保证的顺序,在单独的线程上发送通知,或者可以保证其子类遵循此顺序,因为它们选择。
当新创建一个可观察对象时,其观察者集合为空。 当且仅当equals方法为它们返回true时,两个观察者被认为是相同的。
主要属性和方法
//是否变化
private boolean changed = false;
//存放观察者容器
private Vector obs;
/** Construct an Observable with zero Observers. */
//构造函数
public Observable() {
//初始化观察者容器
obs = new Vector<>();
}
void addObserver(Observer o)
将一个观察者添加到该对象的观察者组中,前提是它与集合中已有的一些观察者不一样。
int countObservers()
返回此 Observable对象的观察者数。
void deleteObserver(Observer o)
从该对象的观察者组中删除观察者。
void deleteObservers()
清除观察者列表,使此对象不再有任何观察者。
boolean hasChanged()
测试此对象是否已更改。
protected void setChanged()
将此Observable对象标记为已更改; hasChanged方法现在将返回true 。
protected void clearChanged()
表示此对象已不再更改,或已经通知其所有观察者其最近更改,以便 hasChanged方法现在将返回 false 。
void notifyObservers()
如果该对象发生了变化,由所示 hasChanged方法,则通知其所有观察者,并调用 clearChanged方法来指示该对象不再改变。
void notifyObservers(Object arg)
如果该对象发生了变化,由所示 hasChanged方法,则通知其所有观察者,并调用 clearChanged方法来指示该对象不再改变。
属性
1)Observable含有一个boolean型的变量changed,代表是否发生改变,Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,这是因为每个业务中对变化的具体定义不一样,因此需要子类来判断是否发生变化;该变量既提供了一种抽象(变与不变),同时提供了一种观察者更新状态的可延迟加载,通过后面的notifyObservers方法分析可知观察者是否会调用update方法,依赖于changed变量,因此即使被观察者在逻辑上发生改变了,只要不调用setChanged,update是不会被调用的。如果我们在某些业务场景不需要频繁触发update,则可以适时调用setChanged方法来延迟刷新。
2)Observable含有一个集合类Vector,该类泛型为Observer,主要用来存放所有观察自己的对象的引用,以便在更新时可以遍历集合中的观察者,逐个调用update方法。
方法
1)操作changed变量的方法为setChanged()设置变化状态,clearChanged()清除变化状态,这两个的访问权限都是protected,表明这两个方法由子类去调用,由子类来决定是否告诉观察者发生变化了,什么时候变化消失。都有同步关键字保证变量的读写操作线程安全。
2)操作Vector类型变量obs的方法为addObserver(Observer o), deleteObserver(Observer o), deleteObservers(),countObservers(),这四个方法分别实现了动态添加观察者,删除观察者,删除所有观察者,获取观察者数量。四个方法的访问权限都是公有的,这是提供给调用者的方法,让调用者来实时动态的控制哪些观察者来观察该被观察对象。
3)公开的方法notifyObservers(),notifyObservers(Object arg),该方法由调用者来操作,用来通知所有的观察者需要做更新操作了。
四、具体实例
“在学校,上课铃声响了,学生进教师上课;下课铃声响了,学生下课休息;放学铃声响了,学生放学。”
实例分析
涉及的对象包括:铃声和学生。学生根据铃声的变化进行相应的响应(上课、下课、放学)。不难发现,学生是观察者,铃声是学生观察的
对象。因此需要建立一个观察者类Listener实现Observer接口,并根据铃声的变化,进行上课、下课、放学。还需要创建一个观察目标,继承Observable。
代码
import java.util.Observable;
import java.util.Observer;
/**
* 观察者
* @author JavaProgress
* date 2020/1/7 19:26
*/
public class Listener implements Observer {
/**
* 学生姓名
*/
private String name;
public Listener(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
switch (arg.toString()) {
case "1":
attendClass();
break;
case "-1":
afterClass();
break;
case "0":
afterSchool();
break;
}
}
/**
* 上课
*/
private void attendClass() {
System.out.println(name + " 上课了");
}
/**
* 下课
*/
private void afterClass() {
System.out.println(name + " 下课了");
}
/**
* 放学
*/
private void afterSchool() {
System.out.println(name + " 下课了");
}
}
import java.util.Observable;
/**
* 观察目标
*
* @author JavaProgress
* date 2020/1/7 19:36
*/
public class RingSubject extends Observable {
/**
* 铃声
*
* @param num 1上课;-1下课;0放学
*/
public void ring(String num) {
switch (num) {
case "1":
System.out.println("上课铃响了");
break;
case "-1":
System.out.println("下课铃响了");
break;
case "0":
System.out.println("放学铃响了");
break;
}
//改变
setChanged();
//通知观察者
notifyObservers(num);
}
}
/**
* 观察者模式测试
*
* @author JavaProgress
* date 2020/1/7 19:43
*/
public class Test {
public static void main(String[] args) {
//创建观察目标
RingSubject ringSubject = new RingSubject();
for (int i = 0; i < 50; i++) {
//创建观察者
Listener listener = new Listener(String.valueOf(i + 1));
//观察目标添加观察者
ringSubject.addObserver(listener);
}
//调用,铃声响起
ringSubject.ring("0");
}
}
五、总结
5.1 观察者的优点
5.1.1、观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
5.1.2、观察者模式在观察目标和观察者之间建立一个抽象的耦合。
5.1.3、观察者模式支持广播通信。
5.1.4、观察者模式符合“开闭原则”的要求。
5.2 观察者的缺点
5.2.1、如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
5.2.2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
5.2.3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
5.3 观察者的使用场景
5.3.1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
5.3.2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
5.3.3、一个对象必须通知其他对象,而并不知道这些对象是谁。
5.3.4、需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。