跳转至

设计模式之观察者模式

参考:

观察者模式是一种 行为设计模式 ,它定义了一种 一对多的依赖关系 ,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己

核心思想:解耦被观察者和观察者,使其可以独立变化。被观察者无需关心观察者的具体实现,只需维护一个观察者列表并调用统一的接口通知它们。

一、核心概念

Subject (主题): 被观察的对象 - 维护一个观察者列表,保存所有订阅的观察者 - 定义观察者的注册、删除和通知接口。

Observer (抽象观察者): 定义了一个更新接口 - 定义接收通知的接口(如 update() 方法)。

ConcreteSubject (具体主题): - 实现主题接口,存储具体状态,当自身状态变化时,通知所有注册的观察者。

ConcreteObserver (具体观察者): 实现观察者更新接口 - 实现抽象观察者的接口,处理接收到的通知,通常会依赖主题的状态。

二、类图结构

Text Only
@startuml
interface Subject {
    - observers: List
    + attach(observer)
    + detach(observer)
    + notify()
}

class ConcreteSubject {
    - state: Object
}

interface Observer {
    + update(subject)
}

class ConcreteObserver {
    + update(subject)
    + display()
}

Subject <|.. ConcreteSubject : implements
Subject "1" *-- "*" Observer : has
Observer <|.. ConcreteObserver : implements
@enduml

三、实现方式

观察者模式的实现方式可以分为两种:

  • 同步阻塞实现方式:当主题状态改变时,观察者的更新方法会立即执行,这种方式可能会导致阻塞,尤其是在观察者的更新操作比较耗时的情况下。
  • 异步非阻塞实现方式:观察者的更新操作在独立的线程或任务中执行,这样可以避免阻塞问题。通常推荐使用异步非阻塞的方式实现观察者模式。

四、代码示例

1. 抽象主题

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

interface Subject {
    void attach(Observer observer); // 注册观察者
    void detach(Observer observer); // 移除观察者
    void notifyObservers(); // 通知所有观察者
}

class WeatherSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String weatherState; // 天气状态(温度、湿度等)

    @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(this); // 传递主题自身,让观察者获取状态
        }
    }

    // 主题状态变更时调用通知
    public void setWeatherState(String state) {
        this.weatherState = state;
        notifyObservers(); // 状态变化,触发通知
    }

    public String getWeatherState() {
        return weatherState;
    }
}

2. 抽象观察者

Java
interface Observer {
    void update(Subject subject); // 接收通知的方法
}

class WeatherDisplay implements Observer {
    @Override
    public void update(Subject subject) {
        if (subject instanceof WeatherSubject) {
            WeatherSubject weatherSubject = (WeatherSubject) subject;
            System.out.println("天气更新:" + weatherSubject.getWeatherState());
        }
    }
}

3. 使用示例

Java
public class Client {
    public static void main(String[] args) {
        WeatherSubject weatherSubject = new WeatherSubject();
        WeatherDisplay display1 = new WeatherDisplay();
        WeatherDisplay display2 = new WeatherDisplay();

        // 注册观察者
        weatherSubject.attach(display1);
        weatherSubject.attach(display2);

        // 主题状态变更,触发通知
        weatherSubject.setWeatherState("晴转多云"); // 输出:天气更新:晴转多云
        weatherSubject.setWeatherState("暴雨预警");   // 输出:天气更新:暴雨预警

        // 移除观察者
        weatherSubject.detach(display2);
        weatherSubject.setWeatherState("天气转晴");   // 仅 display1 收到通知
    }
}

五、优缺点

优点:

  • 解耦:支持松耦合,主题和观察者可以独立变化
  • 支持广播通信,主题可以一次通知多个观察者
  • 扩展:遵循开闭原则,可以新增观察者而不修改主题

缺点:

  • 性能问题:观察者过多时通知可能耗时
  • 观察者之间不知道彼此存在,可能导致更新冲突
  • 如果观察者更新失败,主题无法感知
  • 通知顺序不确定:观察者执行顺序由主题控制,可能引发依赖问题。
  • 内存泄漏风险:若观察者未正确移除,可能导致主题持有无效引用。
  • 循环依赖:若主题和观察者互相依赖,可能引发死锁或逻辑错误。

六、应用场景

一对多依赖:当一个对象的状态变化需要通知多个对象,且对象间关系动态变化时(如消息订阅、事件监听)。

解耦需求:希望主题和观察者松耦合,彼此无需知道具体实现,只需依赖抽象接口。

实时更新:需要实时反映数据变化的场景(如股票行情、天气监控、UI 组件联动)。

  • 事件处理系统:如GUI中的按钮点击事件
  • 发布-订阅系统:如消息队列、新闻推送
  • 模型-视图-控制器(MVC):模型变化时自动更新视图
  • 监控系统:当监控目标状态变化时触发警报