观察者模式:让对象之间保持“默契”
观察者模式,说白了就是解决“一对多”的依赖问题。一个对象状态变了,其他依赖它的对象能自动收到通知。我最早接触这个模式是在做GUI开发的时候,按钮点击一下,好几个地方都要响应——那时候我就想,这要是硬编码写死,后期维护起来得多痛苦。
你想想看,如果每次新增一个需要接收通知的模块,都要去修改被观察者的代码,那系统就太脆弱了。观察者模式就是把这个过程解耦,让主题(Subject)和观察者(Observer)之间只通过接口打交道。
模式的核心结构
观察者模式其实就四个角色:
- 主题接口(Subject):定义注册、移除、通知观察者的方法
- 具体主题(ConcreteSubject):维护观察者列表,状态变化时通知所有观察者
- 观察者接口(Observer):定义更新方法,供主题调用
- 具体观察者(ConcreteObserver):实现更新方法,处理收到的通知
嗯,这里要注意:主题和观察者之间是松耦合的。主题只知道观察者实现了某个接口,但不知道观察者具体是谁、内部怎么处理。这就是面向接口编程的好处。
核心思想:不要让我主动去问你有没有变化,而是你有变化了主动告诉我。
Java实现示例
先看主题接口和观察者接口:
// 观察者接口
public interface Observer {
void update(String eventType, Object data);
}
// 主题接口
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String eventType, Object data);
}
具体主题的实现:
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
// 状态变了,通知所有观察者
notifyObservers("stateChanged", state);
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String eventType, Object data) {
for (Observer observer : observers) {
observer.update(eventType, data);
}
}
}
具体观察者:
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String eventType, Object data) {
System.out.println(name + " 收到事件: " + eventType + ", 数据: " + data);
}
}
客户端使用:
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("观察者A");
Observer observer2 = new ConcreteObserver("观察者B");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setState("新状态1");
subject.setState("新状态2");
}
}
C++实现示例
C++版本要注意内存管理和线程安全。我在项目中遇到过一个问题:观察者回调里把自己从列表中移除了,结果迭代器失效了。嗯,这个坑后面会细说。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& eventType, void* data) = 0;
};
// 主题接口
class Subject {
public:
virtual ~Subject() = default;
virtual void registerObserver(Observer* observer) = 0;
virtual void removeObserver(Observer* observer) = 0;
virtual void notifyObservers(const std::string& eventType, void* data) = 0;
};
// 具体主题
class ConcreteSubject : public Subject {
private:
std::vector<Observer*> observers;
std::string state;
public:
void setState(const std::string& newState) {
state = newState;
notifyObservers("stateChanged", &state);
}
void registerObserver(Observer* observer) override {
observers.push_back(observer);
}
void removeObserver(Observer* observer) override {
auto it = std::find(observers.begin(), observers.end(), observer);
if (it != observers.end()) {
observers.erase(it);
}
}
void notifyObservers(const std::string& eventType, void* data) override {
for (auto observer : observers) {
observer->update(eventType, data);
}
}
};
// 具体观察者
class ConcreteObserver : public Observer {
private:
std::string name;
public:
ConcreteObserver(const std::string& name) : name(name) {}
void update(const std::string& eventType, void* data) override {
std::string* stateData = static_cast<std::string*>(data);
std::cout << name << " 收到事件: " << eventType
<< ", 数据: " << *stateData << std::endl;
}
};
事件监听与发布订阅模型
观察者模式在实际工程中,最常见的变体就是事件监听和发布订阅模型。我个人习惯把这两者区分开:
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 通信方式 | 直接调用 | 通过消息代理 |
| 耦合度 | 主题知道观察者存在 | 双方完全解耦 |
| 适用场景 | 单一应用内部 | 分布式系统、跨进程 |
| 实现复杂度 | 低 | 中高 |
事件监听其实就是观察者模式在GUI领域的特化。比如Java的Swing里,按钮加个ActionListener,本质上就是注册了一个观察者。发布订阅则更进一步,引入了事件通道(Event Channel)或者消息队列,生产者和消费者完全不知道对方的存在。
我的建议:如果只是单个进程内的模块间通信,用观察者模式就够了。引入消息中间件反而增加了复杂度,除非你有跨进程或异步的需求。
观察者模式的核心流程图
下面这张图展示了观察者模式的完整流程,从注册到通知,一目了然:
避坑指南
我曾经在一个金融交易系统里用过观察者模式,当时踩了几个坑,分享给你:
- 循环依赖:观察者A更新时又触发了主题变化,主题再通知观察者B,B又反过来影响A。这就死循环了。解决方案是加一个更新标志位,或者用事件队列异步处理。
- 内存泄漏:观察者注册了但忘记移除,主题一直持有观察者的引用。在Java里这会导致观察者无法被GC回收。我建议在观察者的析构函数或finalize方法里自动移除自己。
- 通知顺序依赖:有些业务场景依赖观察者被通知的顺序。但观察者模式不保证顺序。如果顺序很重要,可以用优先级队列或者显式排序。
- 异常处理:一个观察者的update方法抛异常,会不会影响其他观察者?我在C++项目里就遇到过,一个观察者崩溃导致整个通知链中断。建议在通知循环里加try-catch,或者用独立的线程池处理。
特别注意:在多线程环境下,观察者列表的遍历和修改可能并发执行。我曾经在Java里用CopyOnWriteArrayList解决了这个问题,但要注意写操作的性能开销。
实际应用场景
观察者模式在真实项目里随处可见:
- GUI事件处理:按钮点击、鼠标移动、键盘输入,都是观察者模式的典型应用
- 数据绑定:MVVM架构里,ViewModel变化自动更新View,底层就是观察者模式
- 日志系统:日志产生器是主题,不同的日志处理器(文件、控制台、远程)是观察者
- 消息通知:订单状态变化后,短信、邮件、推送等多个渠道同时收到通知
我个人习惯在系统设计初期,如果发现某个对象的变化需要通知多个其他对象,就会优先考虑观察者模式。它比硬编码回调要灵活得多,也比引入消息队列要轻量得多。
说白了,观察者模式就是让对象之间保持一种“默契”——你变了,我自然知道。这种默契建立在接口契约之上,而不是具体的实现之上。嗯,这就是它经久不衰的原因。
一句话总结:观察者模式定义了对象之间一对多的依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。