设计模式:观察者模式

观察者模式的定义

观察者模式(Observer Pattern),也叫发布/订阅模式(Publish/Subscribe),定义如下:

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

下面是观察者模式的通用类图:

QQ20161204-0@2x.png

下面说明一下这几个角色的名称:

  • Subject

    被观察者:它必须能够动态地增加和取消观察者。一般是抽象类或者实现类,它的职责是管理观察者并通知观察者。

  • Observer

    观察者:在接收到消息后,调用update()方法进行更新操作,对接收到的消息进行处理。

  • ConcreteSubject

    具体的被观察者:定义了自己的业务逻辑,同时定义对哪些事件进行通知。

  • ConcreteObserver

    具体的观察者:具有不同的处理逻辑,每个观察者在接收到消息后,可以通过自己的逻辑进行处理。

通用代码

先看一下被观察者的通用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Subject {
// 观察者集合
private List<Observer> observers = new ArrayList<Observer>();
// 增加一个观察者
public synchronized void addObserver(Observer observer) {
this.observers.add(observer);
}
// 删除一个观察者
public synchronized void deleteObserver(Observer observer) {
this.observers.remove(observer);
}
public void notifyObservers(){
for (Observer o : observers) {
o.update();
}
}
}

可以看到,被观察者的工作内容非常简单,主要就是增加、删除观察者以及通知观察者。

具体的被观察者代码:

1
2
3
4
5
6
7
8
9
public class ConcreteSubject extends Subject{
public void work(){
// do something ...
super.notifyObservers();
}
}

因为是继承自Subject,所以只需要定义自己的业务逻辑,调用父类的notifyObservers方法即可。

观察者的通用代码:

1
2
3
public interface Observer {
public void update();
}

只需要一个update方法,下面是具体的观察者代码:

1
2
3
4
5
6
7
public class ConcreteObserver implements Observer {
@Override
public void update() {
// do something ...
}
}

下面是使用场景代码:

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.addObserver(observer);
subject.work();
}
}

很简单,都不用注释了。

观察者模式的优缺点

根据上面介绍的观察者模式的定义,可以看下具体的优缺点

优点

  • 观察者和被观察值之间是抽象的耦合关系,如此设计很容易扩展观察者和被观察者
  • 实现了一套触发机制,可以看到,在ConcreteSubject中的work方法中,可以通知观察者,并触发对应的update方法,这里并不关心每一个观察者具体是怎么处理的,它要做的只是通知观察者。
  • 支持广播通信,因为被观察者中可以增加和删除观察者,这样在通知时会通知所有的观察者

缺点

  • 如果一个被观察者对象有很多的观察者,那么通知观察者将会很耗时
  • 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。这是很严重的问题,这一点特别要注意
  • 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的

Java中的观察者模式

在JDK中,已经提供了观察者模式,主要是java.util.Observerbal实现类和java.util.Observer接口,也就是说我们可以不用单独写一个观察者模式,直接使用JDK提供的即可。

下面写一个具体的例子来说明一下JDK中观察者模式的使用。

假设我要实现一个温度监控的功能,当温度超过26°C的时候,通知主人关窗,并且通知空调启动。

很简单,有一个温度计类,是被观察者,定义如下:

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
public class Thermometer extends Observable {
// 温度
private int temperature;
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
/*
* 监控温度
*/
public void monitor(){
if (temperature > 25) {
System.out.println("温度高于25°C,通知观察者...");
// 温度变化
super.setChanged();
// 通知观察者
super.notifyObservers(temperature);
}
}
}

有家里的主人,是观察者:

1
2
3
4
5
6
7
public class Man implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("主人收到消息,温度是" + arg + "°C");
System.out.println("关闭窗户...");
}
}

还有一个观察者,是空调:

1
2
3
4
5
6
7
public class AirConditioner implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("空调收到消息,温度是" + arg + "°C");
System.out.println("启动空调...");
}
}

看注释已经能够明白是怎么回事了,下面看下他们之间的类图:

QQ20161204-1.png

可以看到,一个Observer包含了多个ObservableManAirConditioner作为观察者都实现了Observer接口,Thermometer作为被观察者,继承了Observable

下面看一下场景类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) {
Thermometer thermometer = new Thermometer();
Observer man = new Man();
Observer airConditioner = new AirConditioner();
// 添加观察者
thermometer.addObserver(man);
thermometer.addObserver(airConditioner);
// 设置温度为26°C
thermometer.setTemperature(26);
// 开始监控
thermometer.monitor();
}
}

运行结果如下:

1
2
3
4
5
温度高于25°C,通知观察者...
空调收到消息,温度是26°C
启动空调...
主人收到消息,温度是26°C
关闭窗户...

总结

观察者模式的实际应用有很多,比如Tomcat中的生命周期,就是典型的观察者模式,了解了观察者模式之后,再要了解Tomcat的生命周期就会非常容易了。

再比如现实生活中的例子,点一份外卖,点的商品相当于被观察者,商家和送餐员相当于观察者,点餐时,通知商家下订单,并且也会通知送餐员去商家取餐,而点餐的你也相当于观察者,可以查看送餐的进度。所以,观察者模式的用途也是比较广泛的。