一、观察者模式:从“盯梢”到“通知”的思维转变

观察者模式,说白了就是解决“一个对象状态变了,其他一堆对象怎么知道”的问题。

我刚开始接触这个模式时,觉得它不就是个“广播”嘛。后来在项目中踩过坑才明白,它背后藏着更深的软件设计哲学——发布-订阅模型松耦合思想

今天咱们就把它拆开揉碎了讲清楚。

1.1 观察者模式的定义

官方定义是这样的:定义对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新

嗯,听起来有点绕。我换个说法:

  • 被观察者(Subject):就是那个“被盯着的对象”,它状态变了要喊一嗓子。
  • 观察者(Observer):就是那些“盯着别人”的对象,收到通知后做自己的事。

举个例子。你在写一个天气预报系统。天气数据变了(温度、湿度),UI界面要刷新,数据库要存储,报警系统要判断是否发预警。如果每个模块都去轮询天气数据,那代码就乱成一锅粥了。

观察者模式的做法是:天气数据作为被观察者,UI、数据库、报警系统都注册成观察者。天气一变,自动通知它们。

核心思想:被观察者不关心谁在观察自己,观察者也不依赖被观察者的具体实现。它们只通过一个“通知接口”通信。

1.2 发布-订阅模型:观察者模式的“升级版”

观察者模式有两种常见实现方式:

  • 经典观察者模式:观察者直接注册到被观察者上。被观察者维护一个观察者列表。
  • 发布-订阅模型:中间加一个“事件通道”或“消息代理”。发布者和订阅者不直接接触。

我个人习惯把发布-订阅模型看作是观察者模式的“解耦加强版”。

为什么会这样?你想想看,经典模式里,被观察者还得维护一个观察者列表。如果观察者多了,或者观察者之间还有依赖关系,维护起来就头疼了。

发布-订阅模型引入了一个中间层。发布者只管发消息,订阅者只管收消息。中间层负责路由和分发。这样,发布者和订阅者完全不知道对方的存在。

我在项目中遇到过这样一个场景:一个电商系统,用户下单后,需要通知库存系统扣库存、通知物流系统生成运单、通知积分系统加积分、通知短信系统发通知。如果用经典观察者模式,订单服务要维护四个观察者。后来改成发布-订阅模型,订单服务只管往消息队列里丢一条“订单创建”事件,其他系统自己去订阅。清爽多了。

我的建议:如果观察者数量固定且变化少,用经典模式就够了。如果观察者可能动态增减,或者系统需要跨进程通信,果断上发布-订阅模型。

1.3 松耦合设计思想:观察者模式的灵魂

观察者模式最牛的地方,不是它怎么通知,而是它怎么不通知

什么意思?

松耦合的核心是:被观察者不需要知道观察者的任何细节。它只知道观察者实现了某个接口(比如叫 update)。至于观察者收到通知后是刷新UI、写数据库、还是发邮件,被观察者一概不管。

这带来的好处很明显:

  • 可扩展性:加一个新观察者,不需要改被观察者的代码。写个新类,注册进去就行。
  • 可维护性:观察者出问题了,不会影响被观察者和其他观察者。
  • 可复用性:被观察者可以在不同场景下复用,只要观察者接口不变。

我曾经踩过一个坑:在一个监控系统中,被观察者直接调用了观察者的具体方法。后来要加一个新的报警方式,得改被观察者的代码。改完发现,原来那个观察者也被影响了。嗯,从那以后,我再也不敢让被观察者直接依赖具体观察者了。

注意:松耦合不是“无耦合”。观察者和被观察者之间仍然存在接口依赖。这个接口要设计得足够稳定,否则一改接口,两边都得改。

1.4 代码示例:一个简单的观察者模式

咱们用Java写个最简单的例子。天气数据作为被观察者,两个观察者:显示面板和报警系统。

// 观察者接口
interface Observer {
    void update(float temperature, float humidity);
}

// 被观察者接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// 具体被观察者:天气数据
class WeatherData implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;
    private float humidity;

    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        notifyObservers();  // 数据变了,通知大家
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity);
        }
    }
}

// 具体观察者:显示面板
class DisplayPanel implements Observer {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("显示面板更新:温度=" + temperature + ", 湿度=" + humidity);
    }
}

// 具体观察者:报警系统
class AlarmSystem implements Observer {
    @Override
    public void update(float temperature, float humidity) {
        if (temperature > 40) {
            System.out.println("报警:温度过高!");
        }
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        DisplayPanel panel = new DisplayPanel();
        AlarmSystem alarm = new AlarmSystem();

        weatherData.registerObserver(panel);
        weatherData.registerObserver(alarm);

        weatherData.setMeasurements(38.5f, 65.0f);
        weatherData.setMeasurements(42.0f, 70.0f);  // 触发报警
    }
}

你看,WeatherData 只调用了 Observer 接口的 update 方法。它不知道 DisplayPanel 和 AlarmSystem 具体干了什么。这就是松耦合。

1.5 观察者模式的知识体系

下面这张图帮你理清观察者模式的核心逻辑:

观察者模式知识体系 被观察者 (Subject) 观察者接口 (Observer) 具体观察者A 具体观察者B 具体观察者C 通知 被观察者通过观察者接口通知所有注册的观察者,实现松耦合

1.6 观察者模式 vs 发布-订阅模型

我把它们的区别整理成一张表,方便你对比:

对比维度 经典观察者模式 发布-订阅模型
通信方式 观察者直接注册到被观察者 通过中间事件通道通信
耦合度 观察者与被观察者仍有接口依赖 发布者和订阅者完全解耦
适用场景 单进程、观察者数量固定 跨进程、观察者动态增减
实现复杂度 低,直接维护列表 高,需要消息代理
典型应用 GUI事件监听、MVC架构 消息队列、事件驱动架构

1.7 避坑指南:我踩过的三个坑

最后,分享几个我实际项目中遇到的坑,希望能帮你少走弯路。

  • 坑一:观察者通知顺序依赖。我曾经以为观察者收到通知的顺序是固定的,结果在代码里写了顺序依赖。后来换了个JDK版本,顺序变了,程序就崩了。记住:永远不要依赖观察者的通知顺序
  • 坑二:观察者内部抛异常。一个观察者update方法里抛了异常,导致其他观察者收不到通知。后来我加了try-catch,确保一个观察者出问题不影响其他人。
  • 坑三:忘记注销观察者。在Web应用里,用户登录后注册了观察者,退出时没注销。结果观察者列表越来越大,内存泄漏了。嗯,从那以后,我每次注册观察者都会在finally块里确保注销。

观察者模式看起来简单,但用好了能让你的系统灵活很多。关键是理解它背后的松耦合思想——让对象之间只通过接口通信,不依赖具体实现。这样,你的代码才能经得起变化。


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