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

前言

你是否遇到过这种场景?

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

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

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

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

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


问题背景

天气监测系统

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

直男式硬编码实现

java 复制代码
// 主题类:天气数据,负责管理数据和通知
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);
    }
}

代码问题全解

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

核心思想

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

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

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


实现分析

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

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

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

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

核心三点:

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

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


模式结构详解

Subject:抽象主题/被观察者

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

必要性分析

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

设计原则

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

代码实现

java 复制代码
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

ConcreteSubject:具体主题/被观察者

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

必要性分析

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

代码实现

java 复制代码
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()方法,无需关心观察者的具体实现,减少依赖

设计原则

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

代码实现

java 复制代码
public interface Observer {
    void update(float temperature);
}

ConcreteObserver:具体观察者

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

必要性分析

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

代码实现

java 复制代码
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 不依赖具体的 PhoneDisplayWebDisplay,仅依赖 Observer 接口。
  • 动态扩展 :新增显示设备(如 LEDDisplay)时,只需实现 Observer 接口并注册到主题,无需修改 WeatherData
  • 简化维护 :状态变更逻辑集中在 WeatherData,观察者自行处理更新。

进阶应用

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

推数据(Pushvs 拉数据(Pull

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

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

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

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

选择依据

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

代码优化实现

java 复制代码
// 推模型:传递事件对象(封装复杂数据)
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
  • 观察者更新的线程竞争:多个线程同时触发通知,观察者可能处理过时数据。

解决方案

java 复制代码
// 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组件、请求处理器)。

    java 复制代码
    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

    java 复制代码
    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) + 引用队列:适用于需要精准控制观察者回收的高级场景。

    java 复制代码
    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

java 复制代码
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

java 复制代码
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模拟订单通知

java 复制代码
// 生产者服务(订单服务)
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的事件机制

java 复制代码
// 定义事件
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 的观察者模式

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

核心代码实现

dart 复制代码
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 同步更新。

自定义观察者模式

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

核心代码实现

dart 复制代码
// 观察者接口定义
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 方案 ,大多数场景下,StreamStreamBuilder 的组合能够简化代码。

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

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


总结

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

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

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

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

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

相关推荐
大胃粥10 分钟前
Android V app 冷启动(9) Activity 生命周期调度
android
IT专家-大狗26 分钟前
Edge浏览器安卓版流畅度与广告拦截功能评测【不卡还净】
android·前端·edge
一笑的小酒馆30 分钟前
AndroidRom定制删除Settings某些菜单选项
android
火柴就是我1 小时前
android drawText 绘制 数字 注意点
android
帅次1 小时前
Flutter ListView 详解
android·flutter·ios·iphone·webview
匹马夕阳2 小时前
(二十六)Java观察者模式在Android开发中的应用详解
android·java·观察者模式
百锦再3 小时前
Android Drawable 目录下的 XML 图形文件详解
android·xml·java·app·手机·安卓
百锦再3 小时前
Android ImageButton 使用详解
android·java·app·安卓·studio·mobile
JarvanMo4 小时前
如何在Flutter中保护密钥文件?
前端·flutter
顾林海4 小时前
深度解析CopyWriteArrayList工作原理
android·java·面试