在软件开发中,我们经常遇到这样的场景:一个动作发生后,需要同时触发一系列后续操作。
如果你把这些操作全部写死在一个方法里,代码就会变成一团乱麻(所谓的“面条代码”),牵一发而动全身。今天,我们通过一个生动的直播间送礼案例,来聊聊如何用观察者模式优雅地解决这个问题。
1. 场景引入:不仅是送礼那么简单
假设你正在开发一个直播 App 的核心功能——送礼。 当用户送出一个“火箭”时,后台不仅仅是扣钱那么简单,通常还需要触发以下一堆子业务:
粉丝团系统:给用户增加粉丝团经验值。
抽奖系统:判断这次送礼是否触发了抽奖资格。
PK系统:如果主播正在打 PK,需要给 PK 条增加分数。
特效系统:在公屏上播放炫酷动画。
[问题] 糟糕的实现方式(紧耦合)
如果不使用设计模式,你的GiftService代码很可能是这样的:
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代码实现:
// 观察者接口 public interface GiftObserver { void onGiftSent(User user, Gift gift); }第二步:实现具体的观察者 (ConcreteObserver)
各个子系统实现接口,编写自己的逻辑。
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代码实现:
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)只管印报纸并分发,它不知道也不关心是张三还是李四在读报纸。
当你发现系统中有**“牵一发而动全身”、“一对多联动”**的业务场景时,请果断使用观察者模式。它能让你的核心逻辑保持纯净,让复杂的业务链路变得井井有条。