周六加了一天班,周日这觉,小王睡到了自然醒。
醒来没急着起。鸡蛋、牛奶,昨天买的坚果和核桃也到了,拆开抓一把,早餐就算齐了。吃完又在床上躺了会儿。
窗外天气很好。待会儿去赶个集,再顺路剪个头发。这天正好是十一月第一天,日子没安排得多满,但每一样都让人松快。

包裹到了,早餐添了一把坚果,人也终于歇过来了
坚果不是他一遍遍刷物流刷来的。包裹到站,手机收到提醒,他才知道东西到了。
这背后是个很省心的思路:你没空盯着所有事情,先说一句"有变化告诉我",然后各自去过日子。
今天聊的就是这件事:观察者(Observer)------一个对象的状态变了,主动通知那些关心它的对象。系列第八篇。
01 别隔一会儿就问一次
回头看这次等快递的过程,有个动作很熟:点开物流,看一眼,还是"运输中";过一会儿又点开,还是那一条。
代码里也能这么等:
java
while (!parcel.hasArrived()) {
Thread.sleep(60_000); // 每隔一分钟问一次
}
System.out.println("可以去取坚果了");
包裹没动静,你却得一直问
这叫轮询。它能解决问题,但代价摆在那儿:问得太勤,白白消耗资源;问得太慢,到了也不能马上知道。要是等的不止一个包裹,每个都守着一个循环,代码很快就乱了。
换一种思路:别让我一直问你。你到了,告诉我。
这就是从"我反复查你的状态",变成"你状态变了,通知我"。

一个把眼睛留在物流页面上,一个把注意力还给自己的生活
02 先订阅,到货再通知
一个包裹到站,可能有两件事要做:给小王发个取件提醒,再往站点台账里记一笔。
包裹不需要知道手机长什么样,也不需要懂台账怎么记。它只留一份"关心到货的人"的名单。
java
// 关心到货的人,都实现这个接口
interface ArrivalObserver {
void onArrived(String parcelName);
}
// 被观察者:包裹
class Parcel {
private final String name;
private final List<ArrivalObserver> observers = new ArrayList<>();
Parcel(String name) { this.name = name; }
void subscribe(ArrivalObserver observer) {
observers.add(observer);
}
void unsubscribe(ArrivalObserver observer) {
observers.remove(observer);
}
void arrive() {
System.out.println(name + " 已到货");
for (ArrivalObserver observer : observers) {
observer.onArrived(name); // 挨个通知
}
}
}
// 观察者一:手机提醒
class PhoneAlert implements ArrivalObserver {
public void onArrived(String parcelName) {
System.out.println("手机提醒:" + parcelName + " 可以取了");
}
}
// 观察者二:站点台账
class ArrivalLedger implements ArrivalObserver {
public void onArrived(String parcelName) {
System.out.println("站点台账:登记 " + parcelName);
}
}
包裹只认 ArrivalObserver,不认手机和台账
用的时候,先把订阅关系建好:
java
Parcel nuts = new Parcel("坚果和核桃");
PhoneAlert phone = new PhoneAlert();
nuts.subscribe(phone);
nuts.subscribe(new ArrivalLedger());
nuts.arrive(); // 到货,两个观察者都收到通知
nuts.unsubscribe(phone); // 不想收了,也可以退订
先登记谁关心,状态变了再挨个通知
这里有两个角色。Parcel 是被观察者,管自己的状态,也管订阅名单;PhoneAlert 和 ArrivalLedger 是观察者,收到通知后各做各的事。

一个状态变化,多个观察者各自响应
观察者模式的核心不是"手机会响",而是状态变化的一方和接收通知的一方,不必互相知道细节。
03 包裹不用认识每一个听消息的人
如果不用观察者,arrive() 很可能写成这样:
java
void arrive() {
phoneAlert.send(name);
arrivalLedger.record(name);
}
明天要再记一项库存,就回来加一行;后天要换个提醒方式,又回来改一遍。包裹本来只管到没到,最后却认识了一堆跟它没关系的家伙。
换成观察者以后,新增一个 StockCounter,让它实现 ArrivalObserver,再订阅包裹就行,Parcel 的代码不用改。
这就是它解耦的地方:被观察者只依赖一个通知接口,具体谁来听、来了以后做什么,由观察者自己决定。
不过,这个类比有一处得挑明。
真实的到货提醒跨着网络,背后可能有物流系统、推送服务和失败重试。上面这段代码只是在一个进程里调用几个方法,不会自己联网,也不保证谁一定收到。
对得上的,只有"先订阅,变化后通知"这层关系。别把一条手机推送背后的整套系统,都塞进观察者模式里。
04 通知里,到底装什么
刚才的通知只带了一个包裹名:
java
void onArrived(String parcelName);
手机提醒够用,但如果台账还需要运单号、到站时间,再往后每加一项就改一次接口,所有观察者都得跟着改。
常见的做法,是把"发生了什么"装成一个事件对象:
java
interface ArrivalObserver {
void onArrived(ParcelArrivedEvent event);
}
ParcelArrivedEvent 里放这次通知真正需要的数据。以后补充字段,至少不用先改方法参数列表。
但也不是把整个 Parcel 一股脑交出去就完事。观察者只需要一个到站时间,就别顺手把修改地址、撤回包裹的能力也递过去。事件该带什么,不看对象里有什么,看接收方真的需要什么。
这套思路在 Java 里很常见。图形界面按钮上的 ActionListener,是"被点击了通知我";属性变化后,代码调用 PropertyChangeSupport 的 firePropertyChange(),它再通知注册的监听器;Spring 的应用事件则把发布和监听交给容器组织。
名字不同,先登记、后通知这一步没变。
05 叫了,不等于对方办妥了
再看 arrive() 里那个 for 循环。它不是按一下广播按钮就结束,而是在当前线程里,挨个调用观察者的方法。
这带来几个很实际的问题。
手机提醒如果执行得慢,后面的台账就得等。某个观察者抛出异常,如果没人接住,后面的观察者可能收不到,arrive() 也会跟着中断。至于谁先谁后,更不该靠名单的排列顺序碰巧满足业务要求。
所以,观察者模式解决的是"该通知谁",不自动解决"通知失败了怎么办"。
需要异步执行,就明确引入异步机制;需要跨服务、持久化、重试或削峰,就得考虑消息队列等基础设施。那些能力不是实现一个 Observer 接口就附送的。
AI 可以很快把直接调用改成事件通知,但同步还是异步、失败要不要重试、重复通知怎么处理,仍然得由人定。
06 什么时候,别急着发通知
如果一个操作固定只有一个后续动作,而且调用顺序本来就该清清楚楚,直接调用通常更好:
java
parcel.markArrived(); // 只改状态,不发通知
arrivalLedger.record(parcel); // 固定的一步,直接调用
两行从上到下读完就懂,犯不上先找谁订阅了,再追通知跳到了哪里。
观察者适合的是另一种情况:状态变化的一方不该知道谁关心它,而关心它的对象可能是零个、一个,也可能是多个。
别给每个字段变化都发事件,也别让事件里继续发事件,绕成一条看不见的链。写起来很省事,排查时却可能连"这行到底是谁触发的"都找不到。
退订也一样重要。对象已经不需要收通知了,还留在名单里,轻则会收到无用消息,重则会让旧对象一直被引用,没法释放。
07 去过自己的日子
吃完早餐,小王又躺了一会儿。手机放在一边,没一直刷物流。
包裹已经到了,剩下是自己的安排:去赶个集,再把头发剪短些。外头天气正好,出门也不用做太多心理建设。
十一月的第一天,没什么大阵仗。睡够了,吃好了,出门走一走,就挺像个新开始。
跟前面七篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把要做的事做成对象,代理管谁能碰到真东西,观察者管状态变了告诉谁。
带走一句话:观察者 = 先登记谁关心,状态变了,再挨个通知。
别一直守着问。把该订阅的订阅好,去过自己的日子。
好事未必都有到货提醒,但新的一月,愿它们一件一件,接踵而来。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)------观察者模式的原始定义:定义对象间的一对多依赖,当一个对象状态改变时,所有依赖者都得到通知并自动更新
- 《Head First 设计模式》第 2 章------用气象站讲清主题、观察者,以及推数据和拉数据的区别
- Java 官方文档 · PropertyChangeSupport------管理属性变化监听器,并在代码显式触发后向它们发送通知
- Spring 官方文档 · 应用事件与监听器------容器中的事件发布、监听与同步处理机制