简介
观察者模式(Observer Pattern)又叫作发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependent)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖它的对象都会得到通知并被自动更新,属于行为型设计模式。
通用模板
-
创建抽象主题:指被观察的对象(IObservable)。该角色是一个抽象类或接口,定义了增加、删除、通知观察者对象的方法。主题就是被观察者,它是用户感兴趣的事情,需要持有一个观察者(一般是个接口或抽象类)引用
// 抽象主题 public interface ISubject { // 注册观察者(观察者是接口) void registerObserver(IObserver observer); // 移除观察者 void removeObserver(IObserver observer); // 事情发生的时候通知观察者 void notifyObservers(); // 更新信息 void updateInfo(String info); }
-
创建具体主题::具体被观察者,当其内部状态变化时,会通知已注册的观察者。
import java.util.ArrayList; import java.util.List; // 具体主题,就是被观察者也就是用户感兴趣的事情 public class ConcreteSubject implements ISubject { private List<IObserver> observers = new ArrayList<>(); private String info; @Override public void registerObserver(IObserver observer) { observers.add(observer); System.out.println("注册观察者"); } @Override public void removeObserver(IObserver observer) { observers.remove(observer); System.out.println("移除观察者"); } @Override public void notifyObservers() { for (IObserver observer : this.observers) { observer.update(info); System.out.println("观察者更新了信息:" + info); } } @Override public void updateInfo(String info) { this.info = info; notifyObservers(); } }
-
创建观察者接口:定义了响应通知的更新方法。一般所有观察者都是接口或者抽象类
// 抽象观察者 public interface IObserver { void update(String info); }
-
创建具体的观察者:当得到状态更新的通知时,会自动做出响应。
// 具体观察者,就是用户感兴趣的事情,用户订阅某个主题后,只要有更新就得通知观察者 public class ConcreteObserver implements IObserver { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(String info) { System.out.println(this.name + "接收到信息:" + info); } }
模板测试
- 测试代码
// 客户端相当于用户 public class Client { public static void main(String[] args) { // 具体主题 ISubject subject = new ConcreteSubject(); // 创建具体观察者类,这里可以把用户传递进去方便查看是哪个用户订阅了某个主题 IObserver user1 = new ConcreteObserver("用户" + Client.class.getSimpleName()); // 注册观察者,说白了就是把观察者的引用传递给被观察者(主题:用户感兴趣的事情) subject.registerObserver(user1); String info = "报纸A已上架"; // 更新用户感兴趣的主题信息 subject.updateInfo(info); // 移除观察者 subject.removeObserver(user1); // 移除后新的消息将不会通知该用户 subject.updateInfo("报纸A更新"); } }
- 测试结果
注册观察者 用户Client接收到信息:报纸A已上架 观察者更新了信息:报纸A已上架 移除观察者
应用场景
观察者模式在现实生活中的应用也非常广泛,比如,App角标通知、起床闹钟设置,如下图所示,以及GPer生态圈消息通知、邮件通知、广播通知、桌面程序的事件响应等。
在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
观察者模式主要适用于以下应用场景。
(1)当一个抽象模型包含两方面内容,其中一方面依赖另一方面。
(2)其他一个或多个对象的变化依赖另一个对象的变化。
(3)实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
(4)多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
优点
(1)观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则。
(2)分离了表示层(观察者)和数据逻辑层(被观察者),并且建立了一套触发机制,使得数据的变化可以响应到多个表示层上。
(3)实现了一对多的通信机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。
缺点
(1)如果观察者数量过多,则事件通知会耗时较长。
(2)事件通知呈线性关系,如果其中一个观察者处理事件卡壳,则会影响后续的观察者接收该事件。 (3)如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。
“生搬硬套”实战
场景描述
假设你正在开发一个天气预报应用,该应用需要实时更新天气信息,并将最新的天气情况通知给用户。用户可以通过订阅不同的天气站点来获取最新的天气预报。当天气站点更新了天气信息时,所有订阅该站点的用户都应该接收到通知。
代码开发
-
创建抽象主题(主题就是被观察者,这里指天气预报应用系统)
// 主题接口(Subject),它包含添加、移除观察者以及通知观察者的方法 // 主题就是就是被观察者,需要持有观察者引用(一般是接口或抽象类) public interface IWeatherStation { void registerObserver(IWeatherObserver observer); void removeObserver(IWeatherObserver observer); void notifyObservers(); void updateWeatherData(String weatherInfo); }
-
创建具体主题(这里指的就是具体的天气系统实现类)
// 具体的主题类来实现主题接口,并管理观察者的列表。 public class WeatherStationImpl implements IWeatherStation { private List<IWeatherObserver> observers = new ArrayList<>(); private String weatherInfo; @Override public void registerObserver(IWeatherObserver observer) { observers.add(observer); } @Override public void removeObserver(IWeatherObserver observer) { observers.remove(observer); } @Override public void notifyObservers() { for (IWeatherObserver observer : observers) { observer.update(weatherInfo); } } @Override public void updateWeatherData(String weatherInfo) { this.weatherInfo = weatherInfo; notifyObservers(); } }
-
创建观察者接口(这里指更新天气消息的接口,观察者中的方法就是用户感兴趣的事件)
// 观察者接口(Observer),它包含一个更新方法,用于接收主题的通知。 public interface IWeatherObserver { void update(String weatherInfo); }
-
创建具体的观察者(这里指的是更新具体的消息类)
// 具体的观察者类来实现观察者接口,并处理接收到的天气信息 public class WeatherApp implements IWeatherObserver { private String name; public WeatherApp(String name) { this.name = name; } @Override public void update(String weatherInfo) { System.out.println(name + " 接收到天气信息: " + weatherInfo); } }
至此,我们就通过“生搬硬套”观察者模式的模板设计出一套用户通过订阅天气系统而天气发送变化的时候系统会发生案例,接下来我们进行测试:
-
测试代码
public class Test { public static void main(String[] args) { IWeatherStation weatherStation = new WeatherStationImpl(); WeatherApp user1 = new WeatherApp("用户1"); WeatherApp user2 = new WeatherApp("用户2"); weatherStation.registerObserver(user1); weatherStation.registerObserver(user2); // 更新天气信息 weatherStation.updateWeatherData("晴天"); // 用户1退订 weatherStation.removeObserver(user1); // 再次更新天气信息 weatherStation.updateWeatherData("阴天"); } }
-
测试结果
用户1 接收到天气信息: 晴天 用户2 接收到天气信息: 晴天 用户2 接收到天气信息: 阴天
总结
观察者模式的核心是将观察者与被观察者解耦,以类似消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。