16、观察者模式(Observer):发布订阅、事件监听、Spring事件机制、C++信号槽
观察者模式,说白了就是「有人喊一嗓子,一群人响应」。
我在项目中见过太多硬编码的耦合代码——A模块做完一件事,直接调用B、C、D的方法。后来需求一变,改得我头皮发麻。观察者模式就是用来解这个毒的。
16.1 模式本质:一对多的依赖关系
定义很简单:当一个对象(主题)状态发生变化时,所有依赖它的对象(观察者)都会得到通知并自动更新。
你想想看,这像不像公众号的订阅机制?你关注了一个号,它一发文章你就收到推送。你没关注,就收不到。这就是观察者模式的核心思想——发布-订阅。
核心角色:
- Subject(主题):维护观察者列表,提供注册、移除、通知方法
- Observer(观察者):定义更新接口,接收主题的通知
- ConcreteSubject(具体主题):存储状态,状态变化时通知所有观察者
- ConcreteObserver(具体观察者):实现更新接口,处理通知
16.2 Java实现:从手写到Spring事件
16.2.1 手写一个观察者模式
先来个最朴素的版本。我记得刚入行时,组长让我写一个「订单状态变化通知」的功能,我当时就是用的这个套路。
// 观察者接口
public interface Observer {
void update(String message);
}
// 主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// 具体主题:订单状态
public class OrderSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String orderStatus;
public void setOrderStatus(String status) {
this.orderStatus = status;
notifyObservers(); // 状态变了,通知大家
}
@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("订单状态变更为:" + orderStatus);
}
}
}
// 具体观察者:短信通知
public class SmsObserver implements Observer {
@Override
public void update(String message) {
System.out.println("【短信】发送通知:" + message);
}
}
// 具体观察者:邮件通知
public class EmailObserver implements Observer {
@Override
public void update(String message) {
System.out.println("【邮件】发送通知:" + message);
}
}
// 使用
public class Demo {
public static void main(String[] args) {
OrderSubject order = new OrderSubject();
order.attach(new SmsObserver());
order.attach(new EmailObserver());
order.setOrderStatus("已支付"); // 短信和邮件都会收到通知
order.setOrderStatus("已发货");
}
}
避坑指南:我曾经在通知方法里直接遍历观察者列表,结果某个观察者抛了异常,后面的观察者全没收到通知。后来我改成用 CopyOnWriteArrayList 或者在遍历时捕获异常,这才稳了。
16.2.2 Java内置的Observable
其实JDK早就提供了现成的——java.util.Observable 类和 Observer 接口。不过说实话,这个设计有点老了,Java 9 已经标记为废弃。我个人建议还是自己写或者用事件机制。
16.2.3 Spring事件机制
这才是企业级开发中的重头戏。Spring的事件机制本质就是观察者模式,但人家封装得漂亮多了。
// 1. 定义事件
public class OrderEvent extends ApplicationEvent {
private String orderId;
private String status;
public OrderEvent(Object source, String orderId, String status) {
super(source);
this.orderId = orderId;
this.status = status;
}
// getter...
}
// 2. 定义监听器
@Component
public class SmsListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
System.out.println("短信通知:订单" + event.getOrderId() + "状态变为" + event.getStatus());
}
}
// 或者用 @EventListener 注解,更简洁
@Component
public class EmailListener {
@EventListener
public void handleOrderEvent(OrderEvent event) {
System.out.println("邮件通知:订单" + event.getOrderId() + "状态变为" + event.getStatus());
}
}
// 3. 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void updateOrderStatus(String orderId, String status) {
// 业务逻辑...
publisher.publishEvent(new OrderEvent(this, orderId, status));
}
}
你看,Spring把观察者的注册、通知全帮你管了。你只需要定义事件和监听器,剩下的交给容器。我在微服务项目里经常用这个来做「异步解耦」——比如订单完成后发邮件、发短信、更新积分,全用事件驱动,主流程干干净净。
注意:默认情况下,Spring事件是同步的。也就是说,发布事件的线程会阻塞,直到所有监听器执行完毕。如果监听器里有耗时操作,记得加上 @Async 注解异步执行。
16.3 C++实现:信号槽机制
C++标准库没有直接提供观察者模式,但Qt的信号槽(Signal & Slot)机制是这方面的经典实现。我自己在写C++服务端时,也经常用Boost.Signals2来模拟。
16.3.1 手写C++观察者
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 观察者基类
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& message) = 0;
};
// 主题
class Subject {
private:
std::vector<std::shared_ptr<Observer>> observers;
public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void detach(std::shared_ptr<Observer> obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end()
);
}
void notify(const std::string& message) {
for (auto& obs : observers) {
obs->update(message);
}
}
};
// 具体观察者
class SmsObserver : public Observer {
public:
void update(const std::string& message) override {
std::cout << "[SMS] " << message << std::endl;
}
};
class EmailObserver : public Observer {
public:
void update(const std::string& message) override {
std::cout << "[Email] " << message << std::endl;
}
};
int main() {
Subject subject;
auto sms = std::make_shared<SmsObserver>();
auto email = std::make_shared<EmailObserver>();
subject.attach(sms);
subject.attach(email);
subject.notify("订单已支付");
subject.notify("订单已发货");
return 0;
}
16.3.2 用Boost.Signals2实现
手写版本有个问题——线程安全、生命周期管理都得自己操心。Boost.Signals2把这些都封装好了。
#include <iostream>
#include <boost/signals2.hpp>
#include <string>
// 定义信号类型
boost::signals2::signal<void(const std::string&)> orderStatusChanged;
// 槽函数
void onSmsNotify(const std::string& msg) {
std::cout << "[SMS] " << msg << std::endl;
}
void onEmailNotify(const std::string& msg) {
std::cout << "[Email] " << msg << std::endl;
}
int main() {
// 连接信号与槽
auto conn1 = orderStatusChanged.connect(&onSmsNotify);
auto conn2 = orderStatusChanged.connect(&onEmailNotify);
// 发射信号
orderStatusChanged("订单已支付");
orderStatusChanged("订单已发货");
// 断开连接
conn1.disconnect();
conn2.disconnect();
return 0;
}
个人经验:我在一个实时交易系统中用过Boost.Signals2来做行情推送。当时最头疼的是「槽函数执行顺序」——多个观察者之间有依赖关系。Boost.Signals2支持给连接指定优先级,这个特性救了我一命。
16.4 观察者模式的优缺点
| 优点 | 缺点 |
|---|---|
| 解耦:主题和观察者之间是抽象耦合 | 通知顺序不可控(除非显式指定优先级) |
| 支持广播通信:一对多,一劳永逸 | 观察者过多时,通知可能成为性能瓶颈 |
| 符合开闭原则:新增观察者无需修改主题 | 可能引起循环依赖(A通知B,B又通知A) |
| 支持异步处理(配合消息队列更佳) | 观察者如果持有主题引用,容易造成内存泄漏 |
16.5 避坑指南
我踩过的坑,列出来给你提个醒:
- 内存泄漏:观察者注册后忘记注销,导致主题持有观察者的引用,GC无法回收。尤其是用匿名内部类或Lambda时,要格外小心。
- 通知风暴:一个状态变化触发多个观察者,每个观察者又触发新的状态变化……我曾经在调试时看到日志刷了上万行,才发现是循环通知。
- 异常处理:通知过程中某个观察者抛异常,后面的观察者全挂。建议每个观察者独立try-catch,或者用线程池异步执行。
- 线程安全:多线程环境下,观察者列表的遍历和修改要加锁,或者用并发容器。
一句话总结:观察者模式是解耦的利器,但用不好就是一把双刃剑。记住——注册要记得注销,通知要防异常,多线程要加锁。
公众号:蓝海资料掘金营,微信deep3321