16、观察者模式(Observer):发布订阅、事件监听、Spring事件机制、C++信号槽

观察者模式,说白了就是「有人喊一嗓子,一群人响应」。

我在项目中见过太多硬编码的耦合代码——A模块做完一件事,直接调用B、C、D的方法。后来需求一变,改得我头皮发麻。观察者模式就是用来解这个毒的。

16.1 模式本质:一对多的依赖关系

定义很简单:当一个对象(主题)状态发生变化时,所有依赖它的对象(观察者)都会得到通知并自动更新

你想想看,这像不像公众号的订阅机制?你关注了一个号,它一发文章你就收到推送。你没关注,就收不到。这就是观察者模式的核心思想——发布-订阅

核心角色:

  • Subject(主题):维护观察者列表,提供注册、移除、通知方法
  • Observer(观察者):定义更新接口,接收主题的通知
  • ConcreteSubject(具体主题):存储状态,状态变化时通知所有观察者
  • ConcreteObserver(具体观察者):实现更新接口,处理通知
观察者模式结构图 Subject(主题接口) + attach(Observer) + detach(Observer) + notify() Observer(观察者接口) + update() ConcreteSubject - observers: List - state: State + getState() ConcreteObserver - subject: Subject + update() 观察者注册 通知所有观察者

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