【Flutter 状态管理 - 伍】 | 万字长文解锁你对观察者模式的认知

737 阅读18分钟

image.png

前言

你是否遇到过这种场景?
某个核心数据一变,就得像催债一样挨个调用十几处关联模块的更新方法。新增一个功能,就得在原始类里硬塞一行调用代码。时间一长,类膨胀成庞然大物,维护代码像在沼泽里挣扎 —— 越改越陷得深。

观察者模式就是来治这个病的。它把这种“单向依赖”的硬编码,变成“自由订阅”的灵活关系。数据发布方不再关心谁要听消息,监听方也不用死等轮询。就像微信群的@所有人:想听的进群,嫌吵的退群,群主发完消息就能甩手走人。

GUI事件到微服务通信,从Spring框架到Kafka消息队列,观察者模式的影子无处不在。它不是银弹,但绝对是解开“数据变动连锁反应”的一把关键钥匙。

本文前部分基于Java代码讲述,后部分基于Dart代码讲述,通过各种不同的实践增强对观察者模式的理解!!!

千曲而后晓声,观千剑而后识器。虐它千百遍方能通晓其真意


问题背景

天气监测系统

在学习观察着模式之前,我们先来看这样一个需求:有一个天气监测系统,当温度变化时,需要更新多个显示设备(如手机、网页)。

直男式硬编码实现

// 主题类:天气数据,负责管理数据和通知
class WeatherData {
    private float temperature;
    
    // 直接依赖具体观察者对象
    private PhoneDisplay phoneDisplay = new PhoneDisplay();
    private WebDisplay webDisplay = new WebDisplay();

    // 数据变化时,手动调用所有观察者的更新方法
    public void setMeasurements(float temperature) {
        this.temperature = temperature;
        // 硬核连环调用
        phoneDisplay.update(temperature);
        webDisplay.update(temperature);
    }
}

// 观察者实现类:写死具体逻辑
class PhoneDisplay {
    public void update(float temperature) {
        System.out.println("[手机] 温度:" + temperature);
    }
}

class WebDisplay {
    public void update(float temperature) {
        System.out.println("[网页] 温度:" + temperature);
    }
}

// 使用示例:代码一跑,生死难料
public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        weatherData.setMeasurements(25.5f);
    }
}

代码问题全解

image.png 问题1: WeatherData 类直接绑定 PhoneDisplayWebDisplay 等具体类。如果此时增加了一个新需求,添加一个 SmartWatchDisplay?那就必须改 WeatherData 的代码,动一处而崩全局。如果想删一个 WebDisplay?注释掉一行代码,系统就敢给你甩个空指针异常。

本质:代码紧耦合到窒息,违背面向对象设计的依赖倒置原则「依赖抽象,而非实现」。

问题2: 每次新增/删除观察者都要修改 WeatherData 类。若需求变更时,开发者像在雷区拆弹 —— 稍有不慎全盘皆炸;系统维护成本指数级上升,改个需求等于重写代码。

本质:修改及维护困难,违背面向对象设计的开闭原则「对扩展开放,对修改关闭」。

问题3: setMeasurements 方法里重复调用 update。 若有10个观察者?写10xxx.update(),程序员变打字员;并且逻辑分散,改个参数得在所有调用处同步修改,漏一处就埋雷。

本质:冗余的硬编码调用。

问题4: 观察者列表写死在代码里。如果用户想运行时关闭手机通知?除非重启服务,否则没门;若临时加个日志观察者?改代码编译上线祈祷别出Bug

本质:无法动态管理观察者。

问题5: WeatherData 和显示逻辑深度绑定。想复用 WeatherData 做股票行情系统?只能重写,CV大法都救不了;相似功能重复造轮子,团队开发效率直接砍半。

本质:代码复用性差。


灵魂拷问:为什么这种代码能活下来?

场景1:产品经理要求加个「温度过高自动报警」功能。

  • 程序员被迫在 WeatherData 里插入一行 alarm.update()
  • 结果手滑写成 alamr.update(),系统深夜疯狂报错,运维提刀上门。

场景2:团队新人接手代码,看到 WeatherData 类里密密麻麻的 update() 调用。

  • 新人颤抖着问:“这代码能重构吗?”
  • 老鸟点烟冷笑:“你敢动这里,明天就提离职吧。”

这种代码该判死刑吗?

直接问题: 紧耦合、难扩展、重复代码、无法动态管理。

深层危害: 团队协作噩梦,开发效率低下,系统稳定性如履薄冰。

唯一出路重构为观察者模式,用抽象接口解耦,让代码能呼吸、能生长、能抗揍。


本质定义

观察者模式Observer Pattern)是一种行为设计模式,用于在对象之间建立一对多的依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会自动收到通知并更新。

image.png

核心思想

  • 1、解耦被观察者与观察者:被观察者仅依赖观察者的抽象接口,而非具体实现,降低两者之间的直接耦合。
  • 2、动态订阅机制:观察者可以随时注册或移除对主题的依赖,系统行为在运行时动态调整。
  • 3、事件驱动模型:当主题状态变化时,自动触发通知,观察者被动响应事件(而非主动轮询)。

这一模式的关键在于将变化的传播逻辑(通知)与业务逻辑(状态更新)分离,使系统更灵活、可扩展。

本质是通过抽象依赖关系,实现对象间的一对多动态通知机制。


实现分析

观察者模式的核心目标是解耦被观察者与观察者,而解耦的关键是依赖抽象接口而非具体实现。

基于这一原则,该模式通常会细化为四个角色:

image.png

实现上述目标的一种比较直观的方式如下:

image.png

此形式就像「订阅-推送」机制。举个栗子:你关注公众号(注册),后台把你的账号存进列表(容器)。公众号发新文章(被观察者变化),自动群发通知所有粉丝(通知)。你取关(撤销注册)后就不再接收消息。

核心三点:

  • 1、注册:观察者主动加入被观察者的通知列表。
  • 2、通知:被观察者变化时,自动遍历列表触发所有观察者的更新。
  • 3、解耦:被观察者只认接口(比如能收消息的能力),不关心具体观察者是谁,Android用户和iOS用户都能订阅同一个号。

优势:灵活扩展,新增或移除观察者无需修改被观察者代码,适合数据变动触发多对象联动的场景(如UI更新消息推送)。


模式结构详解

Subject:抽象主题/被观察者

职责:定义观察者的注册移除通知接口。

必要性分析

  • 为所有具体主题提供统一的规范操作
  • 观察者只需依赖Subject接口,而非具体主题类,​降低耦合度

设计原则

  • 依赖倒置(抽象化:通过接口抽象类定义主题的行为,而非依赖具体实现。
  • 单一职责:仅负责管理观察者触发通知,不处理具体业务逻辑。

代码实现

public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

ConcreteSubject:具体主题/被观察者

职责:实现Subject接口,管理具体状态,并在状态变化时触发通知。

必要性分析

  • 抽象主题无法直接持有状态或实现业务逻辑,必须由具体类完成。
  • 不同主题(如天气站、股票行情)可能有不同的状态管理方式。

代码实现

class WeatherData implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;

    public void setTemperature(float temperature) {
        this.temperature = temperature;
        notifyObservers();
    }

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

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

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

Observer:观察者

职责:定义统一的更新接口(如update()),供主题调用。

必要性分析

  • 为所有具体观察者(如显示屏、日志模块)提供统一的响应规范。
  • 主题只需调用update()方法,无需关心观察者的具体实现,减少依赖

设计原则

  • 接口隔离:仅暴露必要的更新方法,避免观察者依赖无关逻辑。
  • 开闭原则:新增观察者类型时,无需修改主题代码。

代码实现

public interface Observer {
    void update(float temperature);
}

ConcreteObserver:具体观察者

职责:实现Observer接口,定义具体的响应逻辑。

必要性分析

  • 抽象观察者无法直接实现业务逻辑(如显示温度、记录日志)。
  • 不同观察者对同一事件可能有不同的处理方式。

代码实现

class PhoneDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("[手机] 温度: " + temperature + "°C" );
    }
}

class WebDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("[网页] 温度: " + temperature + "°C");
    }
}

// 使用示例
public class Main {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        PhoneDisplay phone = new PhoneDisplay();
        WebDisplay web = new WebDisplay();

        weatherData.registerObserver(phone);
        weatherData.registerObserver(web);

        weatherData.setTemperatures(25.5f);

        weatherData.removeObserver(web);

        weatherData.setTemperature(26.5f);
    }
}

输出:
[手机] 温度: 25.5°C
[网页] 温度: 25.5°C
[手机] 温度: 26.5°C

解决的问题

  • 解耦WeatherData 不依赖具体的 PhoneDisplay 或 WebDisplay,仅依赖 Observer 接口。
  • 动态扩展:新增显示设备(如 LEDDisplay)时,只需实现 Observer 接口并注册到主题,无需修改 WeatherData
  • 简化维护:状态变更逻辑集中在 WeatherData,观察者自行处理更新。

进阶应用

目标:解决实际开发中的扩展性性能安全等问题。

推数据(Pushvs 拉数据(Pull

推模型:主题将数据直接传递给观察者(通过方法参数)。

public interface Observer {
    void update(float temperature); // 推模型:参数传递数据
}

拉模型:观察者主动从主题获取数据(通过主题的公共方法)。

public interface Observer {
    void update(); // 观察者调用 subject.getTemperature() 拉取数据
}

选择依据

场景推模型拉模型
数据量小适合(如传递温度、状态码)冗余(需多次调用Getter
数据量大或结构复杂冗余(参数臃肿)适合(观察者按需取用)
观察者需要不同数据子集不灵活(需传递所有数据)灵活(观察者自行选择所需数据)
主题数据变更频繁可能频繁触发推送(资源浪费)观察者控制拉取时机(节省资源)

代码优化实现

// 推模型:传递事件对象(封装复杂数据)
class WeatherEvent {
    private float temperature;
    private float humidity; //增加湿度
    // getters, constructor
}

interface Observer {
    void update(WeatherEvent event); // 推模型优化
}

// 拉模型:主题提供数据访问接口
class WeatherData {
    public float getTemperature() { /* ... */ }
    public float getHumidity() { /* ... */ }
}

class Display implements Observer {
    private WeatherData data;
    
    @Override
    public void update() {
        float temp = data.getTemperature(); // 拉取数据
        float humidity = data.getHumidity();
    }
}

线程安全

核心问题

  • 观察者列表的并发修改:在遍历列表时,其他线程增删观察者,导致ConcurrentModificationException
  • 观察者更新的线程竞争:多个线程同时触发通知,观察者可能处理过时数据。

解决方案

// 1、使用线程安全集合
private List<Observer> observers = new CopyOnWriteArrayList<>(); // 写时复制,避免并发修改

// 2、同步代码块
public synchronized void registerObserver(Observer o) {
    observers.add(o);
}

public void notifyObservers() {
    List<Observer> copy;
    synchronized (this) {
        copy = new ArrayList<>(observers); // 复制快照
    }
    for (Observer o : copy) {
        o.update();
    }
}

// 3、异步通知
private ExecutorService executor = Executors.newFixedThreadPool(4);

public void notifyObservers() {
    for (Observer o : observers) {
        executor.submit(() -> o.update()); // 异步执行
    }
}

防止内存泄漏

核心问题

  • 观察者未注销:观察者对象被主题长期引用,无法被垃圾回收。
  • 主题生命周期过长:静态主题或单例主题持有的观察者可能一直存活。

解决方案

  • 1、显式注销观察者:适用于观察者生命周期明确(如UI组件、请求处理器)。

    class ObserverImpl implements Observer {
        private Subject subject;
        
        public ObserverImpl(Subject subject) {
            this.subject = subject;
            subject.registerObserver(this);
        }
        
        public void destroy() {
            subject.removeObserver(this); // 显式注销
        }
    }
    
  • 2、弱引用(WeakReference

    class WeakSubject {
        private List<WeakReference<Observer>> observers = new ArrayList<>();
        
        public void addObserver(Observer o) {
            observers.add(new WeakReference<>(o));
        }
        
        public void notifyObservers() {
            Iterator<WeakReference<Observer>> it = observers.iterator();
            while (it.hasNext()) {
                Observer o = it.next().get();
                if (o != null) {
                    o.update();
                } else {
                    it.remove(); // 自动清理无效引用
                }
            }
        }
    }
    
  • 3、使用虚引用(PhantomReference) + 引用队列:适用于需要精准控制观察者回收的高级场景。

    ReferenceQueue<Observer> queue = new ReferenceQueue<>();
    List<PhantomReference<Observer>> refs = new ArrayList<>();
    
    public void addObserver(Observer o) {
        refs.add(new PhantomReference<>(o, queue));
    }
    
    // 定期清理队列中的已回收观察者
    public void clean() {
        Reference<? extends Observer> ref;
        while ((ref = queue.poll()) != null) {
            refs.remove(ref);
        }
    }
    

进阶实战要点速查表

核心问题解决方法典型场景关键细节
数据传递方式推:数据直接塞给观察者
拉:观察者自己动手拿
推送温度/状态码等小数据
拉取数据库查询结果等大块数据
推模式别传可变对象(如集合)
拉模式给观察者开数据查询权限
多线程炸雷用写时复制列表(CopyOnWriteArrayList
异步通知走线程池
高并发订单状态更新
实时数据监控大屏
异步任务加try-catch防崩溃
别在回调里操作原始数据列表
内存泄漏陷阱生命周期结束必须反注册
弱引用兜底(WeakReference
AndroidActivity监听
临时统计模块
RxJavaDisposable统一管理
定期清理弱引用僵尸(搭配ReferenceQueue

高手过招:把观察者模式揉碎了,塞进架构里

事件总线(Event Bus

事件总线就像个快递分拣中心,所有模块把消息往这儿一扔,谁爱听谁自己领。本质上是个升级版观察者模式,但更灵活,支持跨模块、跨层级通信。

场景:订单支付成功后,通知库存、物流、积分系统。

手搓一个乞丐版EventBus

import java.util.*;
import java.util.function.Consumer;

// 事件总线核心
public class EventBus {
    // 事件类型 → 处理函数列表
    private Map<Class<?>, List<Consumer<Object>>> handlers = new HashMap<>();

    // 订阅事件:告诉总线,XX类型的事件来了,就调我这个处理函数
    public <T> void subscribe(Class<T> eventType, Consumer<T> handler) {
        List<Consumer<Object>> list = handlers.computeIfAbsent(eventType, k -> new ArrayList<>());
        list.add((Consumer<Object>) handler); // 类型强转,心要大
    }

    // 发布事件:把事件丢进总线,让订阅的人自己处理
    public <T> void publish(T event) {
        List<Consumer<Object>> list = handlers.get(event.getClass());
        if (list != null) {
            list.forEach(handler -> handler.accept(event));
        }
    }
}

// 事件定义:订单创建事件
class OrderCreatedEvent {
    private String orderId;
    public OrderCreatedEvent(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        EventBus bus = new EventBus();

        // 库存系统订阅订单事件
        bus.subscribe(OrderCreatedEvent.class, event -> {
            System.out.println("库存系统:扣减订单" + event.getOrderId() + "的库存");
        });

        // 物流系统订阅订单事件
        bus.subscribe(OrderCreatedEvent.class, event -> {
            System.out.println("物流系统:为订单" + event.getOrderId() + "生成运单");
        });

        // 用户下单,发布事件
        bus.publish(new OrderCreatedEvent("ORDER_666"));
    }
}

输出:
库存系统:扣减订单ORDER_666的库存  
物流系统:为订单ORDER_666生成运单

响应式编程(ReactiveX):让数据流动起来

响应式编程是观察者模式的超进化体,把数据封装成流(Observable),允许链式操作(过滤、转换、合并),处理异步事件像写流水线一样爽。

场景: 实时股票行情:多个图表动态更新。

RxJava搞个股票行情Demo

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;

// 模拟股票数据源
class StockTicker {
    private final String symbol;
    private double price;

    public StockTicker(String symbol) {
        this.symbol = symbol;
        this.price = 100.0;
    }

    // 每隔1秒生成一个随机价格波动
    public Observable<Double> start() {
        return Observable.interval(1, TimeUnit.SECONDS)
                .map(tick -> {
                    price += (Math.random() - 0.5) * 10; // 随机波动
                    return price;
                });
    }
}

// 使用示例
public class RxDemo {
    public static void main(String[] args) throws InterruptedException {
        StockTicker appleTicker = new StockTicker("AAPL");
        Disposable subscription = appleTicker.start()
                .subscribe(price -> System.out.println("当前价格: " + price));

        // 跑5秒后取消订阅
        Thread.sleep(5000);
        subscription.dispose();
    }
}

输出:
当前价格: 103.4  
当前价格: 98.7  
当前价格: 105.2 

核心特性

  • 链式操作.filter(price -> price > 100).map(price -> "高价警报:" + price)
  • 背压支持:消费者处理不过来时,自动调节数据流速。
  • 线程调度.observeOn(Schedulers.io()) 轻松切换线程。

分布式观察者:让消息跨机器跑

消息队列(如KafkaRabbitMQ)把观察者模式扩展到分布式系统,服务A发消息,服务BCD通过订阅Topic消费消息。

场景:电商大促期间,库存变更同步到多个区域仓库。

Kafka模拟订单通知

// 生产者服务(订单服务)
public class OrderProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        try (Producer<String, String> producer = new KafkaProducer<>(props)) {
            producer.send(new ProducerRecord<>("order-topic", "ORDER_888"));
            System.out.println("订单已发送");
        }
    }
}

// 消费者服务(库存服务)
public class InventoryConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "inventory-group");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumer.subscribe(Collections.singletonList("order-topic"));
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.println("库存系统收到订单:" + record.value());
                }
            }
        }
    }
}

// 消费者服务(物流服务)代码类似,改个group.id即可

核心特性

  • 解耦:订单服务不知道下游是谁,加个促销服务直接订阅就行。
  • 容错:某个服务挂了,消息队列堆积数据,恢复后继续处理。
  • 伸缩:消费者可横向扩展,提升处理能力。

坑点

  • 消息顺序Kafka保证分区内有序,但跨分区可能乱序。
  • 幂等处理:网络重试可能导致消息重复消费,业务逻辑要幂等。

Spring框架的勾搭

Spring的事件机制

// 定义事件
public class OrderEvent extends ApplicationEvent {
    private String orderId;
    public OrderEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }
    public String getOrderId() { return orderId; }
}

// 发布事件
@Service
class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void createOrder() {
        publisher.publishEvent(new OrderEvent(this, "ORDER_123"));
    }
}

// 监听事件
@Component
class InventoryListener {
    @EventListener
    public void handleOrder(OrderEvent event) {
        System.out.println("库存处理订单:" + event.getOrderId());
    }
}

为什么用?

  • Spring生态无缝集成(如结合事务事件)。
  • 适合单应用内模块解耦,不需要引入消息队列。

高手心法

核心原则具体操作踩坑警告
模式不是祖宗,是工具小项目直接用语言内置观察者(如JavaObservable
大系统直接上Kafka/RabbitMQ,别自己写轮子
小项目强上消息队列 = 埋雷
大系统手写观察者 = 找死
解耦要有边界感跨服务通信:用MQ(发订单消息给库存系统)
单应用内:用事件总线(如GuavaEventBus
跨服务用事件总线 = 网络爆炸
单应用强上MQ = 脱裤子放屁
监控比代码重要分布式场景埋点:
消息堆积量监控
消费延迟报警 日志必须带消息ID,方便溯源
不监控 = 睁眼瞎
没日志 = 甩锅无门
设计要留后路哪怕现在只有一个观察者,按事件总线设计
定义明确的事件类(如OrderCreatedEvent
直接写死调用 = 下次改需求重写
事件类字段乱塞 = 后期兼容地狱

Flutter中实现观察者模式

Flutter 中实现观察者模式有两种常见方式,一种是利用 Dart 语言自带的 Stream 处理数据流,另一种则是通过自定义接口和类手动实现观察者逻辑。

基于 Stream 的观察者模式

这种方法利用 Dart 的 StreamController 和 Stream 实现数据监听,适用于需要频繁更新或与 Flutter 响应式框架深度集成的场景。

核心代码实现

import 'dart:async';

// 被观察对象:计数器
class CounterNotifier {
  final StreamController<int> _controller = StreamController();
  int _value = 0;

  Stream<int> get stream => _controller.stream;

  void increment() {
    _value++;
    _controller.sink.add(_value); // 推送新值
  }

  void cleanup() {
    _controller.close(); // 释放资源
  }
}

// 观察者组件
class CounterDisplay extends StatefulWidget {
  const CounterDisplay({super.key});

  @override
  State<CounterDisplay> createState() => _CounterDisplayState();
}

class _CounterDisplayState extends State<CounterDisplay> {
  final CounterNotifier _notifier = CounterNotifier();
  StreamSubscription? _subscription;
  int _currentCount = 0;

  @override
  void initState() {
    super.initState();
    _subscription = _notifier.stream.listen((newValue) {
      setState(() => _currentCount = newValue); // 响应数据变化
    });
  }

  @override
  void dispose() {
    _subscription?.cancel(); // 取消监听
    _notifier.cleanup();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text("当前计数: $_currentCount"),
        ElevatedButton(
          onPressed: _notifier.increment,
          child: const Text('增加'),
        ),
      ],
    );
  }
}

关键点说明

  • CounterNotifier 类通过 StreamController 管理数据流,increment 方法触发数值变化并通知监听者。
  • 组件通过 listen 方法订阅数据流,并在 dispose 生命周期中释放资源,避免内存泄漏。
  • 使用 setState 确保数据变化时 UI 同步更新。

自定义观察者模式

如果需要更灵活的控制(如筛选通知条件或处理复杂状态),可以手动实现观察者模式的结构。

核心代码实现

// 观察者接口定义
abstract class ValueListener {
  void onValueChanged(int value);
}

// 被观察对象
class ValueNotifier {
  final List<ValueListener> _listeners = [];
  int _value = 0;

  void addListener(ValueListener listener) {
    _listeners.add(listener);
  }

  void removeListener(ValueListener listener) {
    _listeners.remove(listener);
  }

  void updateValue(int newValue) {
    _value = newValue;
    _notifyListeners(); // 遍历通知观察者
  }

  void _notifyListeners() {
    for (final listener in _listeners) {
      listener.onValueChanged(_value);
    }
  }
}

// 实现观察者的组件
class ValueDisplay extends StatefulWidget implements ValueListener {
  const ValueDisplay({super.key});

  @override
  State<ValueDisplay> createState() => _ValueDisplayState();
}

class _ValueDisplayState extends State<ValueDisplay> {
  final ValueNotifier _notifier = ValueNotifier();
  int _displayValue = 0;

  @override
  void initState() {
    super.initState();
    _notifier.addListener(this); // 注册监听
  }

  @override
  void dispose() {
    _notifier.removeListener(this); // 移除监听
    super.dispose();
  }

  @override
  void onValueChanged(int value) {
    setState(() => _displayValue = value); // 更新显示
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text("当前值: $_displayValue"),
        ElevatedButton(
          onPressed: () => _notifier.updateValue(DateTime.now().second),
          child: const Text('随机更新'),
        ),
      ],
    );
  }
}

关键点说明

  • ValueNotifier 维护观察者列表,通过 addListener/removeListener 管理订阅关系。
  • updateValue 方法触发数据更新后,遍历所有观察者并调用其 onValueChanged 方法。
  • 组件实现 ValueListener 接口,在 onValueChanged 中更新状态并刷新 UI

两种方案的对比

特性Stream 方案自定义观察者方案
实现复杂度简单(依赖 Dart 原生 API较高(需手动管理观察者列表)
灵活性适用于简单数据流适合需要自定义通知逻辑的场景
内存管理需手动关闭 StreamController需确保移除不再使用的观察者
Flutter 的集成天然支持 setState 和响应式更新需手动调用 setState

食用建议

1、优先选择 Stream 方案 ,大多数场景下,Stream 和 StreamBuilder 的组合能够简化代码。

2、手动实现观察者的适用场景:需要控制通知频率(如防抖、节流)、需要根据条件过滤观察者、与第三方库或遗留代码交互等。

两种方式本质上都是通过解耦数据生产者(被观察者)和消费者(观察者),我们可根据项目需求选择更合适的方案。如果项目已使用 rxdart 等响应式库,也可直接扩展 BehaviorSubject 或 PublishSubject 实现更强大的观察者逻辑。


总结

观察者模式通过抽象主题与观察者的依赖关系,实现了动态、灵活的一对多通信机制。其核心价值在于:

  • 1、解耦:主题与观察者通过接口交互,减少直接依赖。
  • 2、扩展性:支持动态增删观察者,符合开闭原则。
  • 3、事件驱动:以状态变化为驱动,提升系统响应效率。

在实际开发中,需根据场景选择推模型或拉模型,并注意线程安全、性能优化等问题。这一模式是构建事件驱动架构(如GUI微服务实时监控系统)的基石。

熟练掌握好此模式,为我们接下来即将出场的四大状态管理库(ProviderRiverpodBLocGetX)的工作原理打下坚实的基础。敬请期待!!!

欢迎一键四连关注 + 点赞 + 收藏 + 评论