
前言
你是否遇到过这种场景?
某个核心数据一变,就得像催债一样挨个调用十几处关联模块的更新方法。新增一个功能,就得在原始类里硬塞一行调用代码。时间一长,类膨胀成庞然大物,维护代码像在沼泽里挣扎 ------ 越改越陷得深。
观察者模式 就是来治这个病的。它把这种"单向依赖"
的硬编码,变成"自由订阅"
的灵活关系。数据发布方不再关心谁要听消息,监听方也不用死等轮询。就像微信群的@
所有人:想听的进群,嫌吵的退群,群主发完消息就能甩手走人。
从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
类直接绑定 PhoneDisplay
、WebDisplay
等具体类。如果此时增加了一个新需求,添加一个 SmartWatchDisplay
?那就必须改 WeatherData
的代码,动一处而崩全局。如果想删一个 WebDisplay
?注释掉一行代码,系统就敢给你甩个空指针异常。
本质 :代码紧耦合到窒息,违背面向对象设计的依赖倒置原则「依赖抽象,而非实现」。
问题2: 每次新增/删除
观察者都要修改 WeatherData
类。若需求变更时,开发者像在雷区拆弹 ------ 稍有不慎全盘皆炸;系统维护成本指数级上升,改个需求等于重写代码。
本质 :修改及维护困难,违背面向对象设计的开闭原则「对扩展开放,对修改关闭」。
问题3: setMeasurements
方法里重复调用 update
。 若有10
个观察者?写10
行 xxx.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
不依赖具体的PhoneDisplay
或WebDisplay
,仅依赖Observer
接口。 - 动态扩展 :新增显示设备(如
LEDDisplay
)时,只需实现Observer
接口并注册到主题,无需修改WeatherData
。 - 简化维护 :状态变更逻辑集中在
WeatherData
,观察者自行处理更新。
进阶应用
目标 :解决实际开发中的扩展性
、性能
、安全
等问题。
推数据(Push
) vs
拉数据(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组件、请求处理器)。
javaclass ObserverImpl implements Observer { private Subject subject; public ObserverImpl(Subject subject) { this.subject = subject; subject.registerObserver(this); } public void destroy() { subject.removeObserver(this); // 显式注销 } }
-
2、弱引用(
WeakReference
):javaclass 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
) + 引用队列:适用于需要精准控制观察者回收的高级场景。javaReferenceQueue<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 ) |
Android 的Activity 监听 临时统计模块 |
用RxJava 的Disposable 统一管理 定期清理弱引用僵尸(搭配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())
轻松切换线程。
分布式观察者:让消息跨机器跑
用消息队列 (如Kafka
、RabbitMQ
)把观察者模式扩展到分布式系统,服务A
发消息,服务B
、C
、D
通过订阅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
生态无缝集成(如结合事务事件
)。 - 适合单应用内模块解耦,不需要引入消息队列。
高手心法
核心原则 | 具体操作 | 踩坑警告 |
---|---|---|
模式不是祖宗,是工具 | 小项目直接用语言内置观察者(如Java 的Observable ) 大系统直接上Kafka/RabbitMQ ,别自己写轮子 |
小项目强上消息队列 = 埋雷 大系统手写观察者 = 找死 |
解耦要有边界感 | 跨服务通信:用MQ (发订单消息给库存系统) 单应用内:用事件总线(如Guava 的EventBus ) |
跨服务用事件总线 = 网络爆炸 单应用强上MQ = 脱裤子放屁 |
监控比代码重要 | 分布式场景埋点: 消息堆积量监控 消费延迟报警 日志必须带消息ID ,方便溯源 |
不监控 = 睁眼瞎 没日志 = 甩锅无门 |
设计要留后路 | 哪怕现在只有一个观察者,按事件总线设计 定义明确的事件类(如OrderCreatedEvent ) |
直接写死调用 = 下次改需求重写 事件类字段乱塞 = 后期兼容地狱 |
Flutter
中实现观察者模式
Flutter
中实现观察者模式有两种常见方式,一种是利用 Dart
语言自带的 Stream
处理数据流,另一种则是通过自定义接口和类手动实现观察者逻辑。
基于 Stream
的观察者模式
这种方法利用 Dart
的 StreamController
和 Stream
实现数据监听,适用于需要频繁更新或与 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
方案 ,大多数场景下,Stream
和StreamBuilder
的组合能够简化代码。2、手动实现观察者的适用场景:需要控制通知频率(如防抖、节流)、需要根据条件过滤观察者、与第三方库或遗留代码交互等。
两种方式本质上都是通过解耦 数据生产者(被观察者 )和消费者(观察者 ),我们可根据项目需求选择更合适的方案。如果项目已使用 rxdart
等响应式库,也可直接扩展 BehaviorSubject
或 PublishSubject
实现更强大的观察者逻辑。
总结
观察者模式通过抽象主题与观察者的依赖关系 ,实现了动态、灵活的一对多通信机制。其核心价值在于:
- 1、解耦:主题与观察者通过接口交互,减少直接依赖。
- 2、扩展性:支持动态增删观察者,符合开闭原则。
- 3、事件驱动:以状态变化为驱动,提升系统响应效率。
在实际开发中,需根据场景选择推模型或拉模型,并注意线程安全、性能优化等问题。这一模式是构建事件驱动架构 (如GUI
、微服务
、实时监控系统
)的基石。
熟练掌握好此模式,为我们接下来即将出场的四大状态管理库(Provider
、Riverpod
、BLoc
、GetX
)的工作原理打下坚实的基础。敬请期待!!!
欢迎一键四连 (
关注
+点赞
+收藏
+评论
)