rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 观察者模式(Observer Pattern)

  • 一、定义
  • 二、意图
  • 三、结构
  • 四、优点
  • 五、缺点
  • 六、类图(Mermaid)
  • 七、时序图(Mermaid)
  • 八、适用环境
  • 九、模式分析
  • 十、模式扩展
  • 十一、模式应用
  • 十二、Android 中的应用
  • 十三、代码实现(Java 版)
    • 1. 主题接口(Subject)
    • 2. 观察者接口(Observer)
    • 3. 具体主题(WeatherData)
    • 4. 具体观察者 A(温度显示面板)
    • 5. 具体观察者 B(湿度显示面板)
    • 6. 客户端(Client)
    • 7. 运行结果
  • 十四、总结

观察者模式(Observer Pattern)

观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(主题)的状态发生变化时,所有依赖它的对象(观察者)都会自动收到通知并更新。这种模式也被称为 “发布 - 订阅模式”(Publish-Subscribe Pattern),核心是 “解耦主题与观察者,让它们可以独立演化”。

一、定义

官方定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

通俗理解:类比生活中的 “订阅报纸”—— 报社(主题)会向所有订阅者(观察者)推送新报纸,订阅者无需主动询问,只需等待通知即可。例如,天气应用中,“天气数据” 是主题,“温度显示面板”“湿度显示面板” 是观察者,当天气数据更新时,所有面板自动刷新。

主题(Subject/Observable)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。

观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。

被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送事件 给观察者 (Observer), 观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。具体如下图:(类似流水线般流动 & 处理。)

二、意图

  1. 解耦主题与观察者:主题无需知道观察者的具体类型和数量,只需维护一个观察者列表,观察者也无需知道主题的内部实现。
  2. 自动同步状态:当主题状态变化时,所有观察者自动收到通知并更新,确保状态一致性。
  3. 支持动态扩展:可随时添加或移除观察者,无需修改主题或其他观察者的代码(符合开闭原则)。
  4. 分离关注点:主题专注于状态管理和通知,观察者专注于接收通知后的业务处理。

三、结构

观察者模式包含4 个核心角色,各角色协同实现 “状态变化→自动通知→观察者更新” 的流程:

角色名称核心职责
主题(Subject)定义管理观察者的接口: - 注册观察者(registerObserver()); - 移除观察者(removeObserver()); - 通知所有观察者(notifyObservers())。
具体主题(ConcreteSubject)实现主题接口,存储自身状态,当状态变化时调用 notifyObservers() 通知所有观察者。
观察者(Observer)定义接收通知的接口(如 update() 方法),当收到主题通知时更新自身。
具体观察者(ConcreteObserver)实现观察者接口,持有具体主题的引用(可选),在 update() 中根据主题状态执行具体业务逻辑。

四、优点

  1. 低耦合:主题与观察者通过接口通信,彼此无需知道对方的具体实现,降低了模块间的依赖。
  2. 高扩展性:新增观察者只需实现 Observer 接口并注册到主题,无需修改现有代码;新增主题也不影响已有观察者。
  3. 自动同步:主题状态变化时,所有观察者自动更新,避免了手动同步的繁琐和遗漏。
  4. 灵活性:可动态添加 / 移除观察者,适应业务需求的变化(如动态订阅 / 取消订阅)。
  5. 符合开闭原则:对扩展开放(可新增主题 / 观察者),对修改关闭(无需修改现有主题 / 观察者代码)。

五、缺点

  1. 通知顺序不确定:主题通知观察者时,默认按注册顺序执行,但观察者之间可能存在依赖关系,顺序不确定可能导致逻辑错误。
  2. 性能开销:若观察者数量过多,主题通知所有观察者的过程可能耗时较长,影响系统响应速度。
  3. 循环依赖风险:若观察者在 update() 中修改主题状态,可能触发新一轮通知,形成循环依赖,导致栈溢出。
  4. 过度通知:主题状态频繁变化时,观察者会被频繁通知,可能导致不必要的计算或 UI 刷新。

六、类图(Mermaid)

以 “天气监测系统” 为例,类图展示观察者模式的结构:

classDiagram
    direction TB
    class Subject {
        <<interface>>
        +registerObserver(observer: Observer): void
        +removeObserver(observer: Observer): void
        +notifyObservers(): void
    }
    
    class ConcreteSubject {
        -temperature: float
        -humidity: float
        -observers: List~Observer~
        +setMeasurements(temp: float, humidity: float): void
        +getTemperature(): float
        +getHumidity(): float
        +registerObserver(observer: Observer): void
        +removeObserver(observer: Observer): void
        +notifyObservers(): void
    }
    
    class Observer {
        <<interface>>
        +update(): void
    }
    
    class ConcreteObserverA {
        -subject: Subject
        -currentTemp: float
        -currentHumidity: float
        +ConcreteObserverA(subject: Subject)
        +update(): void
        +display(): void
    }
    
    class ConcreteObserverB {
        -subject: Subject
        +ConcreteObserverB(subject: Subject)
        +update(): void
        +display(): void
    }
    
    Subject <|.. ConcreteSubject
    Observer <|.. ConcreteObserverA
    Observer <|.. ConcreteObserverB
    ConcreteSubject o-- Observer : "包含多个"
    ConcreteObserverA o-- Subject : "依赖"
    ConcreteObserverB o-- Subject : "依赖"

七、时序图(Mermaid)

以下时序图展示 “天气数据更新后通知所有观察者” 的过程:

sequenceDiagram
    participant Client
    participant WeatherData as 具体主题(天气数据)
    participant TempDisplay as 具体观察者A(温度面板)
    participant HumidityDisplay as 具体观察者B(湿度面板)
    
    %% 1. 客户端注册观察者
    Client->>TempDisplay: 1. 创建温度面板(关联WeatherData)
    TempDisplay->>WeatherData: 2. 注册观察者(registerObserver(this))
    Client->>HumidityDisplay: 3. 创建湿度面板(关联WeatherData)
    HumidityDisplay->>WeatherData: 4. 注册观察者(registerObserver(this))
    
    %% 2. 主题状态更新
    Client->>WeatherData: 5. 推送新天气数据(setMeasurements(25℃, 60%))
    WeatherData->>WeatherData: 6. 更新内部状态(temperature=25, humidity=60)
    WeatherData->>WeatherData: 7. 触发通知(notifyObservers())
    
    %% 3. 通知所有观察者
    WeatherData->>TempDisplay: 8. 调用 update()
    TempDisplay->>WeatherData: 9. 拉取数据(getTemperature()=25℃)
    TempDisplay->>TempDisplay: 10. 更新显示("当前温度:25℃")
    TempDisplay->>WeatherData: 11. 响应完成
    
    WeatherData->>HumidityDisplay: 12. 调用 update()
    HumidityDisplay->>WeatherData: 13. 拉取数据(getHumidity()=60%)
    HumidityDisplay->>HumidityDisplay: 14. 更新显示("当前湿度:60%")
    HumidityDisplay->>WeatherData: 15. 响应完成
    
    WeatherData->>Client: 16. 所有观察者更新完成

八、适用环境

当系统满足以下场景时,适合使用观察者模式:

  1. 一个对象状态变化需联动更新多个其他对象:如电商订单支付成功后,需通知库存系统减库存、通知积分系统加积分、通知物流系统创建物流单。
  2. 需解耦状态发布者与订阅者:如 GUI 框架中,按钮(发布者)点击事件需通知多个监听器(订阅者),但按钮无需知道监听器的具体逻辑。
  3. 需动态管理订阅关系:如新闻 APP 中,用户可随时订阅 / 取消订阅某类新闻,系统需动态维护订阅列表。
  4. 事件驱动系统:如日志系统(日志事件触发多个处理器)、消息队列(消息发布后被多个消费者处理)。

九、模式分析

  1. 核心逻辑:观察者模式的本质是 “事件驱动 + 动态依赖”—— 主题作为事件源,状态变化时发布事件,观察者订阅事件并响应,依赖关系可动态调整。
  2. 两种通知方式:
    • 推模型:主题主动将变化的数据推送给观察者(如 update(temp, humidity)),观察者被动接收。
    • 拉模型:主题仅通知观察者 “状态已变”,观察者主动从主题拉取数据(如 update() 中调用 getTemperature()),灵活性更高(观察者可按需获取数据)。
  3. 与中介者模式的区别:
    • 观察者模式:一对多依赖,主题直接通知所有观察者,适用于简单联动场景。
    • 中介者模式:多对多依赖,通过中介者转发通知,适用于复杂交互场景(如多个对象相互影响)。
  4. 线程安全问题:若主题和观察者在不同线程,需考虑同步机制(如加锁),避免通知过程中观察者被移除导致的异常。

十、模式扩展

观察者模式可根据业务需求扩展出多种变体:

  1. 带优先级的观察者:观察者注册时指定优先级,主题按优先级顺序通知(解决通知顺序问题)。
  2. 异步通知:主题通过线程池异步通知观察者,避免同步通知导致的性能阻塞(如 Android 的 Handler 机制)。
  3. 粘性事件:新注册的观察者可立即收到主题最近一次的状态(如 Android 的 Sticky Broadcast)。
  4. 观察者分组:主题按类型管理观察者组(如 “天气预警组”“日常天气组”),可针对组进行通知。
  5. 可取消的通知:观察者在 update() 中可返回 “是否继续通知后续观察者”,支持中断通知链。
  6. 结合装饰器模式:动态为观察者添加额外功能(如日志记录、性能统计),不修改原有观察者逻辑。

十一、模式应用

观察者模式是最常用的设计模式之一,广泛应用于框架、库和业务系统:

  1. 编程语言与框架:
    • Java:java.util.Observable 类和 java.util.Observer 接口(已过时,推荐使用 java.beans 包或第三方库)。
    • C#:IObservable 和 IObserver 接口,支持泛型通知。
    • JavaScript:事件监听(addEventListener/removeEventListener)本质是观察者模式。
    • Spring:ApplicationEvent 和 ApplicationListener 实现事件驱动(如 ContextRefreshedEvent)。
  2. GUI 框架:
    • Swing/AWT:按钮 ActionListener、菜单 MenuItemListener 等事件监听机制。
    • Android:View.OnClickListener、OnTouchListener 等视图事件监听。
  3. 消息与事件系统:
    • 消息队列(MQ):如 RabbitMQ、Kafka 的发布 - 订阅模式(Topic)。
    • 前端框架:Vue 的 $on/$emit、React 的 useEffect 状态监听。
  4. 业务系统:
    • 电商订单:支付成功后通知库存、积分、物流系统。
    • 监控系统:指标超阈值时通知告警模块、日志模块、UI 展示模块。

十二、Android 中的应用

Android 框架大量使用观察者模式,以下是典型场景:

  1. 视图事件监听: 按钮、列表等视图的事件监听(如 OnClickListener、OnItemClickListener):

    button.setOnClickListener(new View.OnClickListener() { // 观察者
        @Override
        public void onClick(View v) { // update() 方法
            // 处理点击事件
        }
    });
    // 按钮(主题)被点击时,自动调用所有注册的 OnClickListener 的 onClick()
    
  2. BroadcastReceiver(广播接收者): 系统或应用发送广播(主题发布事件),注册的 BroadcastReceiver(观察者)接收并处理:

    // 注册观察者(广播接收者)
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) { // update()
            // 处理广播事件
        }
    }, new IntentFilter("com.example.MY_ACTION"));
    
    // 发送广播(主题通知)
    sendBroadcast(new Intent("com.example.MY_ACTION"));
    
  3. LiveData 与 ViewModel: Android 架构组件中,LiveData 是主题,Observer 是观察者,数据变化时自动通知 UI 更新:

    // ViewModel 中的 LiveData(主题)
    private MutableLiveData<String> userName = new MutableLiveData<>();
    
    // Activity/Fragment 中观察(注册观察者)
    userName.observe(this, new Observer<String>() {
        @Override
        public void onChanged(String name) { // update()
            // 更新UI显示
            textView.setText(name);
        }
    });
    
    // 更新数据(主题状态变化,自动通知)
    userName.setValue("新用户名");
    
  4. RxJava/RxAndroid: 基于观察者模式的响应式编程库,Observable 是主题,Observer 是观察者,支持复杂的事件流处理:

    Observable.just("Hello") // 主题
        .subscribe(new Observer<String>() { // 观察者
            @Override
            public void onNext(String s) { // update()
                Log.d("RxJava", s);
            }
            // 其他方法(onError, onComplete)
        });
    

十三、代码实现(Java 版)

以 “天气监测系统” 为例,实现观察者模式,包含天气数据(主题)和温度 / 湿度显示面板(观察者):

1. 主题接口(Subject)

import java.util.List;

// 主题接口:定义管理观察者的方法
public interface Subject {
    // 注册观察者
    void registerObserver(Observer observer);
    // 移除观察者
    void removeObserver(Observer observer);
    // 通知所有观察者
    void notifyObservers();
}

2. 观察者接口(Observer)

// 观察者接口:定义接收通知的方法
public interface Observer {
    // 接收主题通知(拉模型:观察者主动从主题获取数据)
    void update();
}

3. 具体主题(WeatherData)

import java.util.ArrayList;
import java.util.List;

// 具体主题:天气数据(存储温度、湿度,状态变化时通知观察者)
public class WeatherData implements Subject {
    private List<Observer> observers; // 观察者列表
    private float temperature; // 温度
    private float humidity;    // 湿度

    public WeatherData() {
        observers = new ArrayList<>();
    }

    // 更新天气数据(触发通知)
    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        notifyObservers(); // 数据更新后通知所有观察者
    }

    // 提供获取数据的方法(供观察者拉取)
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

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

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

    @Override
    public void notifyObservers() {
        // 遍历所有观察者,调用其 update() 方法
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

4. 具体观察者 A(温度显示面板)

// 具体观察者:温度显示面板(显示当前温度)
public class TemperatureDisplay implements Observer {
    private Subject weatherData; // 持有主题引用(用于拉取数据)
    private float currentTemperature;

    // 构造器:注册到主题
    public TemperatureDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this); // 注册为观察者
    }

    @Override
    public void update() {
        // 从主题拉取最新温度
        currentTemperature = ((WeatherData) weatherData).getTemperature();
        display(); // 更新后显示
    }

    // 显示当前温度
    public void display() {
        System.out.println("温度显示面板:当前温度 = " + currentTemperature + "℃");
    }
}

5. 具体观察者 B(湿度显示面板)

// 具体观察者:湿度显示面板(显示当前湿度)
public class HumidityDisplay implements Observer {
    private Subject weatherData;
    private float currentHumidity;

    public HumidityDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update() {
        currentHumidity = ((WeatherData) weatherData).getHumidity();
        display();
    }

    public void display() {
        System.out.println("湿度显示面板:当前湿度 = " + currentHumidity + "%");
    }
}

6. 客户端(Client)

// 客户端:测试天气监测系统
public class WeatherStationClient {
    public static void main(String[] args) {
        // 1. 创建主题(天气数据)
        WeatherData weatherData = new WeatherData();

        // 2. 创建观察者(显示面板)并自动注册到主题
        TemperatureDisplay tempDisplay = new TemperatureDisplay(weatherData);
        HumidityDisplay humidityDisplay = new HumidityDisplay(weatherData);

        // 3. 推送新天气数据(触发通知)
        System.out.println("=== 第一次更新天气数据 ===");
        weatherData.setMeasurements(25.5f, 60.0f);

        // 4. 推送第二次数据
        System.out.println("\n=== 第二次更新天气数据 ===");
        weatherData.setMeasurements(26.8f, 55.0f);

        // 5. 移除湿度面板观察者
        System.out.println("\n=== 移除湿度显示面板 ===");
        weatherData.removeObserver(humidityDisplay);

        // 6. 推送第三次数据(仅温度面板更新)
        System.out.println("\n=== 第三次更新天气数据 ===");
        weatherData.setMeasurements(24.0f, 65.0f);
    }
}

7. 运行结果

=== 第一次更新天气数据 ===
温度显示面板:当前温度 = 25.5℃
湿度显示面板:当前湿度 = 60.0%

=== 第二次更新天气数据 ===
温度显示面板:当前温度 = 26.8℃
湿度显示面板:当前湿度 = 55.0%

=== 移除湿度显示面板 ===

=== 第三次更新天气数据 ===
温度显示面板:当前温度 = 24.0℃

十四、总结

观察者模式通过建立 “主题 - 观察者” 的一对多依赖,实现了状态变化的自动同步和模块解耦,其核心价值在于:

  1. 解耦依赖:主题与观察者通过接口交互,彼此独立演化,降低系统耦合度。
  2. 动态扩展:支持随时添加 / 移除观察者,适应业务需求变化,符合开闭原则。
  3. 事件驱动:以事件为核心,简化了 “一个变化引发多个联动” 场景的实现(如订单支付后的多系统联动)。

在实际开发中,观察者模式是实现 “事件驱动架构” 和 “响应式编程” 的基础,被广泛应用于 GUI 框架、消息系统、状态管理等场景。Android 开发者需重点掌握其在事件监听、LiveData、RxJava 等组件中的应用。

需注意避免通知顺序问题和性能瓶颈:对于复杂场景,可结合优先级队列、异步通知等扩展方式优化;对于多对多交互,可考虑引入中介者模式协调。

总之,观察者模式是处理 “状态联动” 问题的最佳实践,是每个开发者必须掌握的核心设计模式之一。

最近更新:: 2025/10/22 15:36
Contributors: 罗凯文, luokaiwen