观察者模式:让对象之间保持“默契”

观察者模式,说白了就是解决“一对多”的依赖问题。一个对象状态变了,其他依赖它的对象能自动收到通知。我最早接触这个模式是在做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)或者消息队列,生产者和消费者完全不知道对方的存在。

我的建议:如果只是单个进程内的模块间通信,用观察者模式就够了。引入消息中间件反而增加了复杂度,除非你有跨进程或异步的需求。

观察者模式的核心流程图

下面这张图展示了观察者模式的完整流程,从注册到通知,一目了然:

观察者模式核心流程 具体主题 观察者列表(List<Observer>) registerObserver() 观察者A 观察者B 观察者C setState() 状态变化 notifyObservers() update() update() update() ① 观察者先注册到主题 ② 主题状态发生变化 ③ 主题遍历观察者列表 ④ 逐个调用 update() ⑤ 观察者处理通知

避坑指南

我曾经在一个金融交易系统里用过观察者模式,当时踩了几个坑,分享给你:

  • 循环依赖:观察者A更新时又触发了主题变化,主题再通知观察者B,B又反过来影响A。这就死循环了。解决方案是加一个更新标志位,或者用事件队列异步处理。
  • 内存泄漏:观察者注册了但忘记移除,主题一直持有观察者的引用。在Java里这会导致观察者无法被GC回收。我建议在观察者的析构函数或finalize方法里自动移除自己。
  • 通知顺序依赖:有些业务场景依赖观察者被通知的顺序。但观察者模式不保证顺序。如果顺序很重要,可以用优先级队列或者显式排序。
  • 异常处理:一个观察者的update方法抛异常,会不会影响其他观察者?我在C++项目里就遇到过,一个观察者崩溃导致整个通知链中断。建议在通知循环里加try-catch,或者用独立的线程池处理。

特别注意:在多线程环境下,观察者列表的遍历和修改可能并发执行。我曾经在Java里用CopyOnWriteArrayList解决了这个问题,但要注意写操作的性能开销。

实际应用场景

观察者模式在真实项目里随处可见:

  • GUI事件处理:按钮点击、鼠标移动、键盘输入,都是观察者模式的典型应用
  • 数据绑定:MVVM架构里,ViewModel变化自动更新View,底层就是观察者模式
  • 日志系统:日志产生器是主题,不同的日志处理器(文件、控制台、远程)是观察者
  • 消息通知:订单状态变化后,短信、邮件、推送等多个渠道同时收到通知

我个人习惯在系统设计初期,如果发现某个对象的变化需要通知多个其他对象,就会优先考虑观察者模式。它比硬编码回调要灵活得多,也比引入消息队列要轻量得多。

说白了,观察者模式就是让对象之间保持一种“默契”——你变了,我自然知道。这种默契建立在接口契约之上,而不是具体的实现之上。嗯,这就是它经久不衰的原因。

一句话总结:观察者模式定义了对象之间一对多的依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。

公众号:蓝海资料掘金营,微信 deep3321