什么是观察者模式
在《设计模式-可复用面向对象软件基础》一书中,对于观察者模式是这样定义的: 定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
如果你之前对设计模式没有任何了解的话,直接理解上面的定义比较困难,接下来以一个例子来说明观察者模式是解决什么样的问题。
进一步解释定义
四川盆地天气多变,每天上班前我都要根据天气情况来决定是不是应该带雨伞,需不需要准备遮阳帽。所以,我订阅了一个天气通知的服务,在每天早上 7 点钟该服务会告知我当天的天气情况,我根据该信息可以做出自己的决策(是否带伞,是否戴帽)。
这个例子就可以套用观察者模式。观察者模式的核心为:订阅、发布,所以它又被成为发布-订阅模式。在该例子中,“我订阅了一个天气通知的服务”就对应着 订阅 ;“每天早上 7 点钟告知我当天的天气情况”就对应着 发布 。我们习惯将 订阅的对象 称呼为主题(Subject),而 发布的 就是主题的状态(State)。
然后,我们再来尝试理解观察者模式的定义:
- 定义对象间的一种一对多的依赖关系:这里的对象有两类,一类为天气预报这样的服务,一类为像我这样需要天气预报的人;一对多的依赖关系指的是多个像我这样的人都需要天气预报
- 当一个对象的状态发生改变时:这里的对象指的是每天的天气预报,状态就可以理解为晴天、雨天、阴天等天气状态
- 所有依赖于它的对象都能得到通知并被自动更新:这里的对象指的是像我这样订阅了天气预报通知的人
这下应该对观察者模式的定义有了一个比较清晰地认识了。下面看一下类图。
类图
观察者模式定义了一种解决方案,这种解决方案的类图不是唯一的,在有些细节方面可能与其他地方的内容不一样,这很容易理解,具体场景具体分析,但大致思路是一致的。
代码实现
定义主题接口、观察者接口
public interface Subject {
/**
* 注册观察者
* @param observer observer
*/
void registerObserver(Observer observer);
/**
* 注销观察者
* @param observer observer
*/
void unregisterObserver(Observer observer);
/**
* 当主题状态变化时,通知所有观察者
*/
void notifyObservers();
}
public interface Observer {
/**
* 每当观察到的对象发生变化时,都会调用此方法
* @param o 主题状态改变时通知的消息对象
*/
void update(Object o);
}
具体主题实现
public class Weather implements Subject{
private Object state;
private final List<Observer> observers = new ArrayList<Observer>();
/**
* 更新主题状态
* @param state 消息
*/
public void setState(String state) {
this.state = state;
this.notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void unregisterObserver(Observer observer) {
this.observers.remove(observer);
}
@Override
public void notifyObservers() {
this.observers.forEach(o -> o.update(state));
}
}
具体观察者
public class Tom implements Observer {
public Tom(Subject subject) {
subject.registerObserver(this);
}
@Override
public void update(Object o) {
String o1 = (String) o;
String action = o1.contains("下雨") ? "淋着雨上班" : "空手出门";
System.out.println(MessageFormat.format(" Tom 收到消息:{0},{1}", o, action));
}
}
public class Jack implements Observer{
public Jack(Subject subject) {
subject.registerObserver(this);
}
@Override
public void update(Object o) {
String o1 = (String) o;
String action = o1.contains("下雨") ? "今天出门带把伞" : "空手出门";
System.out.println(MessageFormat.format(" Jack 收到消息:{0},{1}", o, action));
}
}
测试代码
public class Client {
public static void main(String[] args) {
// 主题:天气
Weather subject = new Weather();
// 观察者:Jack
new Jack(subject);
// 观察者:Tom
new Tom(subject);
// 主题状态发生变化
subject.setState("2022年5月13日下雨");
subject.setState("2022年5月14日天气晴朗");
}
}
Jack 收到消息:2022年5月13日下雨,今天出门带把伞
Tom 收到消息:2022年5月13日下雨,淋着雨上班
Jack 收到消息:2022年5月14日天气晴朗,空手出门
Tom 收到消息:2022年5月14日天气晴朗,空手出门
分析总结
从上面可以看出,主题中维护了所有的观察者列表,当主题状态改变时,通知所有的观察者。二者的关系松耦合,主题的实现不需要依赖于具体的观察者,当有新的观察者加入时,主题的代码不需要变动。观察者如何处理主题状态的变化也与主题无关(同为雨天,Jack 出门带伞,而 Tom 则淋着雨上班 )。
上面的代码仅仅只是一个观察者模式的示例,在实际开发中,应根据实际情况灵活应对。例如:
在并发情况下,应保证 注册/注销 方法线程安全。
当存在多个主题时,可考虑使用模板类代替主题接口,这样就不用写大量逻辑类似的注册和注销方法。
在 jdk 中,已经提供了观察者模式的实现,他们分别是 java.util.Observer 和 java.util.Observable 。其中,Observable 就是主题的模板类,已经提供了 注册/注销/通知 等核心方法,并且该类已经保证了线程安全。