设计模式之观察者模式
参考:
观察者模式是一种 行为设计模式 ,它定义了一种 一对多的依赖关系 ,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己
核心思想:解耦被观察者和观察者,使其可以独立变化。被观察者无需关心观察者的具体实现,只需维护一个观察者列表并调用统一的接口通知它们。
一、核心概念
Subject (主题): 被观察的对象
- 维护一个观察者列表,保存所有订阅的观察者
- 定义观察者的注册、删除和通知接口。
Observer (抽象观察者): 定义了一个更新接口
- 定义接收通知的接口(如 update() 方法)。
ConcreteSubject (具体主题):
- 实现主题接口,存储具体状态,当自身状态变化时,通知所有注册的观察者。
ConcreteObserver (具体观察者): 实现观察者更新接口
- 实现抽象观察者的接口,处理接收到的通知,通常会依赖主题的状态。
二、类图结构
Text Only |
---|
| @startuml
interface Subject {
- observers: List
+ attach(observer)
+ detach(observer)
+ notify()
}
class ConcreteSubject {
- state: Object
}
interface Observer {
+ update(subject)
}
class ConcreteObserver {
+ update(subject)
+ display()
}
Subject <|.. ConcreteSubject : implements
Subject "1" *-- "*" Observer : has
Observer <|.. ConcreteObserver : implements
@enduml
|

三、实现方式
观察者模式的实现方式可以分为两种:
- 同步阻塞实现方式:当主题状态改变时,观察者的更新方法会立即执行,这种方式可能会导致阻塞,尤其是在观察者的更新操作比较耗时的情况下。
- 异步非阻塞实现方式:观察者的更新操作在独立的线程或任务中执行,这样可以避免阻塞问题。通常推荐使用异步非阻塞的方式实现观察者模式。
四、代码示例
1. 抽象主题
Java |
---|
| import java.util.ArrayList;
import java.util.List;
interface Subject {
void attach(Observer observer); // 注册观察者
void detach(Observer observer); // 移除观察者
void notifyObservers(); // 通知所有观察者
}
class WeatherSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String weatherState; // 天气状态(温度、湿度等)
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this); // 传递主题自身,让观察者获取状态
}
}
// 主题状态变更时调用通知
public void setWeatherState(String state) {
this.weatherState = state;
notifyObservers(); // 状态变化,触发通知
}
public String getWeatherState() {
return weatherState;
}
}
|
2. 抽象观察者
Java |
---|
| interface Observer {
void update(Subject subject); // 接收通知的方法
}
class WeatherDisplay implements Observer {
@Override
public void update(Subject subject) {
if (subject instanceof WeatherSubject) {
WeatherSubject weatherSubject = (WeatherSubject) subject;
System.out.println("天气更新:" + weatherSubject.getWeatherState());
}
}
}
|
3. 使用示例
Java |
---|
| public class Client {
public static void main(String[] args) {
WeatherSubject weatherSubject = new WeatherSubject();
WeatherDisplay display1 = new WeatherDisplay();
WeatherDisplay display2 = new WeatherDisplay();
// 注册观察者
weatherSubject.attach(display1);
weatherSubject.attach(display2);
// 主题状态变更,触发通知
weatherSubject.setWeatherState("晴转多云"); // 输出:天气更新:晴转多云
weatherSubject.setWeatherState("暴雨预警"); // 输出:天气更新:暴雨预警
// 移除观察者
weatherSubject.detach(display2);
weatherSubject.setWeatherState("天气转晴"); // 仅 display1 收到通知
}
}
|
五、优缺点
优点:
- 解耦:支持松耦合,主题和观察者可以独立变化
- 支持广播通信,主题可以一次通知多个观察者
- 扩展:遵循开闭原则,可以新增观察者而不修改主题
缺点:
- 性能问题:观察者过多时通知可能耗时
- 观察者之间不知道彼此存在,可能导致更新冲突
- 如果观察者更新失败,主题无法感知
- 通知顺序不确定:观察者执行顺序由主题控制,可能引发依赖问题。
- 内存泄漏风险:若观察者未正确移除,可能导致主题持有无效引用。
- 循环依赖:若主题和观察者互相依赖,可能引发死锁或逻辑错误。
六、应用场景
一对多依赖:当一个对象的状态变化需要通知多个对象,且对象间关系动态变化时(如消息订阅、事件监听)。
解耦需求:希望主题和观察者松耦合,彼此无需知道具体实现,只需依赖抽象接口。
实时更新:需要实时反映数据变化的场景(如股票行情、天气监控、UI 组件联动)。
- 事件处理系统:如GUI中的按钮点击事件
- 发布-订阅系统:如消息队列、新闻推送
- 模型-视图-控制器(MVC):模型变化时自动更新视图
- 监控系统:当监控目标状态变化时触发警报
六