在软件开发中,我们经常遇到这样的场景:一个动作发生后,需要同时触发一系列后续操作。
如果你把这些操作全部写死在一个方法里,代码就会变成一团乱麻(所谓的"面条代码"),牵一发而动全身。今天,我们通过一个生动的直播间送礼 案例,来聊聊如何用观察者模式优雅地解决这个问题。
1. 场景引入:不仅是送礼那么简单
假设你正在开发一个直播 App 的核心功能------送礼。 当用户送出一个"火箭"时,后台不仅仅是扣钱那么简单,通常还需要触发以下一堆子业务:
-
粉丝团系统:给用户增加粉丝团经验值。
-
抽奖系统:判断这次送礼是否触发了抽奖资格。
-
PK系统:如果主播正在打 PK,需要给 PK 条增加分数。
-
特效系统:在公屏上播放炫酷动画。
[问题] 糟糕的实现方式(紧耦合)
如果不使用设计模式,你的 GiftService 代码很可能是这样的:
Java代码实现:
java
public void sendGift(User user, Gift gift) {
// 1. 核心业务:扣费
walletService.deduct(user, gift.getPrice());
// 2. 杂七杂八的子业务(硬编码在这里)
fanClubService.addExp(user, 10); // 耦合了粉丝团
lotteryService.check(user); // 耦合了抽奖
pkService.addScore(user, gift.getScore()); // 耦合了PK
// 如果明天产品经理说要加个"成就系统",你还得来改这行代码!
}
痛点很明显:
-
违反开闭原则:每次增加或删除子功能,都要修改
sendGift核心代码。 -
维护困难 :
GiftService变得臃肿不堪,且依赖了太多它不该关心的服务。
2. 破局之道:观察者模式
观察者模式 (Observer Pattern) 属于行为型设计模式。
通俗定义 :它定义了一种一对多的依赖关系。当一个对象(主题)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
映射到直播间场景
我们可以利用观察者模式,将"送礼"看作是一个信号 ,其他系统只需要监听这个信号即可。

-
Subject (主题/被观察者) -> 直播间送礼服务 (GiftService)
- 职责:我是大喇叭。我只负责送礼,送完之后喊一声"有人送礼啦",至于谁想知道,你们自己来我这里登记(注册)。
-
Observer (观察者) -> 粉丝团、抽奖、PK系统
- 职责:我是听众。我订阅了送礼事件,一旦听到喇叭喊,我就赶紧干我自己的活。
3. 代码实战:解耦的艺术
让我们重构一下上面的代码。
第一步:定义观察者接口 (Observer)
所有想监听送礼事件的子系统,都必须遵守这个标准。
Java代码实现:
java
// 观察者接口
public interface GiftObserver {
void onGiftSent(User user, Gift gift);
}
第二步:实现具体的观察者 (ConcreteObserver)
各个子系统实现接口,编写自己的逻辑。
Java代码实现:
java
// 粉丝团观察者
public class FanClubObserver implements GiftObserver {
@Override
public void onGiftSent(User user, Gift gift) {
System.out.println("粉丝团:增加经验值 +10");
}
}
// PK系统观察者
public class PkObserver implements GiftObserver {
@Override
public void onGiftSent(User user, Gift gift) {
System.out.println("PK系统:PK条增加分数 " + gift.getScore());
}
}
第三步:改造送礼服务 (Subject)
GiftService 不再依赖具体的子系统,而是维护一个观察者列表。
Java代码实现:
java
public class GiftSubject {
// 1. 维护一个观察者列表(花名册)
private List<GiftObserver> observers = new ArrayList<>();
// 2. 注册方法(订阅)
public void attach(GiftObserver observer) {
observers.add(observer);
}
// 3. 移除方法(取消订阅)
public void detach(GiftObserver observer) {
observers.remove(observer);
}
// 4. 通知方法(广播)
public void notifyObservers(User user, Gift gift) {
for (GiftObserver observer : observers) {
observer.onGiftSent(user, gift);
}
}
// 核心业务逻辑
public void sendGift(User user, Gift gift) {
// 核心逻辑:扣费
System.out.println("钱包:扣除余额 " + gift.getPrice());
// 关键点:只负责通知,不关心具体是谁在听!
notifyObservers(user, gift);
}
}
4. 深度解析:优缺点与注意事项
优点:为什么我们要这么做?
-
极度解耦 :
GiftService甚至不知道PKObserver的存在。两者只依赖于抽象接口。 -
扩展性强 :双十一活动来了,需要送礼掉落碎片?只需新建一个
ActivityObserver并注册进去,核心代码一行都不用改。 -
符合开闭原则:对扩展开放,对修改关闭。
缺点:你需要警惕的坑
-
同步阻塞问题 : 在 Java 的标准实现中,
notifyObservers通常是顺序执行的(同步)。-
如果
FanClubObserver卡顿了 5 秒,后面的PkObserver就得等 5 秒,用户的界面也会卡住。 -
解决方案 :对于非关键业务(如发通知、加经验),建议采用异步处理(放入线程池或消息队列)。
-
-
调试困难: 由于逻辑是分散的,不像面条代码那样一眼能看完,调试时可能需要在一个个观察者之间跳跃,流程比较隐晦。
5. 进阶:Spring 中的观察者模式
在实际的 Java 开发(特别是 Spring Boot)中,我们通常不需要自己手动写 Subject 和 Observer 接口,Spring 已经为我们封装好了事件发布-监听机制,这正是观察者模式的成熟落地。
-
定义事件 :继承
ApplicationEvent(比如GiftSentEvent)。 -
发布者 (Subject) :使用
ApplicationEventPublisher.publishEvent()。 -
监听者 (Observer) :在方法上添加
@EventListener注解。
这种方式比手动写 List 维护更加优雅,且天然支持解耦。
总结
观察者模式就像是生活中的订阅报纸。报社(Subject)只管印报纸并分发,它不知道也不关心是张三还是李四在读报纸。
当你发现系统中有**"牵一发而动全身"、"一对多联动"**的业务场景时,请果断使用观察者模式。它能让你的核心逻辑保持纯净,让复杂的业务链路变得井井有条。