观察者模式

观察者模式

定义

定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

类图

代码示例

我们定义一个场景:热水壶在烧开水,小孩和妈妈都关注烧开水的过程,各自有其处理方法。用while死循环一直轮询虽然可以实现这样的场景,但性能上让人无法接受。

为方便大家copy源码放在本机测试,我将代码写进一个java类中,其它参与类都是非public的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.zhaoyangwoo.observer;
import java.util.ArrayList;
import java.util.List;
/**
* Created by john on 16/8/3.
*/
//定义被观察者接口
interface Subject {
void attachObv(Observer o);
void detachObv(Observer o);
void notifyObvs();
}
//定义观察者接口
interface Observer {
void update(Subject subject);
}
//定义具体被观察者
public class WaterHeater implements Subject {
//存储观察者
private static List<Observer> observerList = new ArrayList<>();
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
private int temperature = 0;
@Override
public void attachObv(Observer o) {
observerList.add(o);
}
@Override
public void detachObv(Observer o) {
observerList.remove(o);
}
@Override
public void notifyObvs() {
observerList.forEach(r -> r.update(this));
}
//模拟烧开水的过程
public void heat() {
temperature = 0;
for (int i = 0; i < 100; i++) {
temperature++;
this.notifyObvs();
try {
//停100ms
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//场景类
public static void main(String[] args) {
WaterHeater waterHeater = new WaterHeater();
Mother mother = new Mother();
Child child = new Child();
waterHeater.attachObv(mother);
waterHeater.attachObv(child);
waterHeater.heat();
waterHeater.detachObv(child);
waterHeater.heat();
System.out.println("烧水结束");
}
}
//具体观察者
class Child implements Observer {
@Override
public void update(Subject subject) {
WaterHeater waterHeater = (WaterHeater) subject;
System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系");
}
}
//具体观察者
class Mother implements Observer {
@Override
public void update(Subject subject) {
WaterHeater waterHeater = (WaterHeater) subject;
if (waterHeater.getTemperature() > 99) {
System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶");
}
}
}

应用场景举例

  • 一个类的状态变化需要通知其他类知晓,并且这些其他类是可以动态配置的
  • 可以实现订阅/发布模型

JDK源码中的模式实现

JDK原生对观察者模式支持。通过java.util.Observablejava.util.Observer两个类很容易实现观察者模式。通过阅读其源码,可以发现原理都是一样的。当然源码里的实现是线程安全的。我们用这两个类重写我们的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// jdk版本的被观察者,不需要自己实现调用通知/注册之类的操作
class WaterHeaterJava extends Observable {
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
private int temperature = 0;
public void heat() {
temperature = 0;
for (int i = 0; i < 100; i++) {
temperature++;
//这里一定要注意,如果要notifyObservers生效,一定要调用setChanged告知已经发生了change,可以通知观察者了.否则notifyObservers不工作
super.setChanged();
super.notifyObservers();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//场景类
public static void main(String[] args) {
WaterHeaterJava waterHeater = new WaterHeaterJava();
MotherJava mother = new MotherJava();
ChildJava child = new ChildJava();
waterHeater.addObserver(mother);
waterHeater.addObserver(child);
waterHeater.heat();
waterHeater.deleteObserver(child);
waterHeater.heat();
System.out.println("Java版烧水结束");
}
}
class ChildJava implements java.util.Observer {
@Override
public void update(Observable o, Object arg) {
WaterHeaterJava waterHeater = (WaterHeaterJava) o;
System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系");
}
}
class MotherJava implements java.util.Observer {
@Override
public void update(Observable o, Object arg) {
WaterHeaterJava waterHeater = (WaterHeaterJava) o;
if (waterHeater.getTemperature() > 99) {
System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶");
}
}
}

关于setChanged,看看它的源码就明白了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
}

思考

思考如下两个问题

性能问题

如果注册的观察者较多,或者观察者处理能力弱、耗时长,那么很可能出现性能问题,毕竟只是简单的遍历调用。

订阅/发布

当然,对于订阅/发布模型的支持有更好的开源框架,各种mq实现了这种模型,并且是异步架构,性能也有保障。

推或拉

大家都喜欢说推模式和拉模式。其实本质上来说都是推模式,这也是使用观察者模式带来的好处。拉模式只不过推了一个引用,可以通过这个引用拿到更多的信息而已。

参考

1.《JAVA与模式》之观察者模式

作者: wuzhaoyang(John)
出处: http://wuzhaoyang.me/
因为作者水平有限,无法保证每句话都是对的,但能保证不复制粘贴,每句话经过推敲。希望能表达自己对于技术的态度,做一名优秀的软件工程师。