设计模式与生活⑧:快递到了,别一直刷物流

周六加了一天班,周日这觉,小王睡到了自然醒。

醒来没急着起。鸡蛋、牛奶,昨天买的坚果和核桃也到了,拆开抓一把,早餐就算齐了。吃完又在床上躺了会儿。

窗外天气很好。待会儿去赶个集,再顺路剪个头发。这天正好是十一月第一天,日子没安排得多满,但每一样都让人松快。

包裹到了,早餐添了一把坚果,人也终于歇过来了

坚果不是他一遍遍刷物流刷来的。包裹到站,手机收到提醒,他才知道东西到了。

这背后是个很省心的思路:你没空盯着所有事情,先说一句"有变化告诉我",然后各自去过日子。

今天聊的就是这件事:观察者(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 去过自己的日子

吃完早餐,小王又躺了一会儿。手机放在一边,没一直刷物流。

包裹已经到了,剩下是自己的安排:去赶个集,再把头发剪短些。外头天气正好,出门也不用做太多心理建设。

十一月的第一天,没什么大阵仗。睡够了,吃好了,出门走一走,就挺像个新开始。

跟前面七篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把要做的事做成对象,代理管谁能碰到真东西,观察者管状态变了告诉谁。

带走一句话:观察者 = 先登记谁关心,状态变了,再挨个通知。

别一直守着问。把该订阅的订阅好,去过自己的日子。

好事未必都有到货提醒,但新的一月,愿它们一件一件,接踵而来。


参考资料

相关推荐
raindayinrain1 天前
c++设计模式
开发语言·c++·设计模式
工一木子1 天前
设计模式与生活⑦:你打的车,隔着一个平台
设计模式
电子科技圈1 天前
2026 RISC-V中国峰会观察:编译调试工具链的“在场节奏”,释放了什么信号?
大数据·人工智能·设计模式·软件工程·软件构建·代码规范·risc-v
电子科技圈1 天前
Arm电机控制开发工具哪家强?
arm开发·单片机·嵌入式硬件·设计模式·软件构建
是2的10次方啊2 天前
设计模式与生活⑦:你打的车,隔着一个平台
设计模式
geovindu2 天前
rust: Read-Write Lock Pattern
开发语言·后端·设计模式·rust·读写锁模式·同步型模式
工一木子3 天前
设计模式与生活⑤:天冷了,给电动车套件挡风被
设计模式
是2的10次方啊3 天前
设计模式与生活⑥:问题一个个砸过来,先写张便签
设计模式
是2的10次方啊4 天前
设计模式与生活⑤:天冷了,给电动车套件挡风被
设计模式