1. 观察者模式:解耦的艺术
第一次接触观察者模式是在一个电商促销系统里。当时我们需要实现一个功能:当商品价格变动时,自动通知所有关注该商品的用户。最初的方案是在商品类里硬编码邮件发送逻辑,结果每次新增通知方式(比如短信、APP推送)都要修改商品类——这简直是个维护噩梦。直到团队里的架构师拍了拍我肩膀说:"小伙子,该用观察者模式了。"
观察者模式(Observer Pattern)是行为型设计模式中的经典,它定义了对象间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动收到通知。就像订报纸——你只需要订阅,报社就会在出新刊时自动派送,而不需要每天打电话去问。
2. 模式结构与核心角色
2.1 UML类图解析
典型的观察者模式包含两个核心接口:
Subject(主题):维护观察者列表,提供注册/注销方法Observer(观察者):定义更新接口,供主题状态变化时调用
具体实现时通常会有:
ConcreteSubject:维护具体状态,状态变更时通知观察者ConcreteObserver:实现更新逻辑,保持与主题状态一致
// 主题接口 interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } // 观察者接口 interface Observer { void update(float price); // 以价格变动为例 }2.2 推模型 vs 拉模型
实际实现中有两种数据传递方式:
- 推模型:主题将变更数据作为参数直接推送给观察者(如上例的update方法)
- 拉模型:观察者收到通知后,主动从主题拉取所需数据(update方法无参数,观察者自行调用主题的get方法)
经验提示:推模型效率更高但不够灵活,拉模型更通用但可能造成不必要的数据访问。在电商案例中,如果只需要传递价格变化,推模型更合适;如果需要完整商品数据,则应该用拉模型。
3. 实战:价格监控系统实现
3.1 具体主题实现
以电商商品为例,实现具体主题:
class Product implements Subject { private List<Observer> observers = new ArrayList<>(); private String name; private float price; // 注册观察者 @Override public void registerObserver(Observer o) { observers.add(o); } // 价格变动时通知所有观察者 public void setPrice(float newPrice) { this.price = newPrice; notifyObservers(); } private void notifyObservers() { for (Observer o : observers) { o.update(price); // 推模型实现 } } }3.2 多种观察者实现
不同通知方式的观察者实现:
// 邮件观察者 class EmailNotifier implements Observer { private String email; public EmailNotifier(String email) { this.email = email; } @Override public void update(float price) { System.out.println("发送邮件到" + email + ":价格已更新为" + price); } } // 短信观察者 class SMSNotifier implements Observer { private String phone; public SMSNotifier(String phone) { this.phone = phone; } @Override public void update(float price) { System.out.println("发送短信到" + phone + ":最新价格¥" + price); } }3.3 客户端使用示例
public class Client { public static void main(String[] args) { Product iphone = new Product("iPhone14", 5999); // 注册观察者 iphone.registerObserver(new EmailNotifier("user1@example.com")); iphone.registerObserver(new SMSNotifier("13800138000")); // 价格变动触发通知 iphone.setPrice(5499); // 双十一降价 } }4. 模式优劣与适用场景
4.1 三大核心优势
- 松耦合:主题和观察者之间抽象耦合,彼此不知道对方的具体实现
- 动态关系:运行时可以随时添加/删除观察者
- 广播通信:一个主题变更可以通知无限数量的观察者
4.2 典型应用场景
- 事件触发机制(如GUI中的按钮点击事件)
- 发布-订阅系统(如消息队列)
- 监控系统(如服务器状态监控)
- 数据同步(如Excel表格与图表联动)
4.3 潜在缺陷与规避方案
内存泄漏风险:观察者注册后若未正确注销,会导致无法被垃圾回收。解决方案:
- 显式调用removeObserver
- 使用WeakReference(Java)或弱引用(其他语言)存储观察者
性能问题:大量观察者时,顺序通知可能成为瓶颈。解决方案:
- 异步通知(但要注意线程安全)
- 批量处理通知
5. 高级应用与模式变体
5.1 事件总线实现
观察者模式的升级版,通过集中式事件总线管理所有事件:
class EventBus { private Map<Class<?>, List<Observer>> handlers = new HashMap<>(); public <E> void register(Class<E> eventType, Observer<E> handler) { handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler); } public <E> void post(E event) { List<Observer> eventHandlers = handlers.get(event.getClass()); if (eventHandlers != null) { eventHandlers.forEach(h -> h.update(event)); } } }5.2 响应式编程中的观察者
现代框架如RxJava、Reactor的核心就是观察者模式的延伸:
Observable.just("Hello") .map(s -> s + " World") .subscribe(System.out::println);这里的subscribe就是注册观察者,而map等操作符链式调用形成了观察者链。
5.3 与其他模式的关系
- 与发布-订阅的区别:发布-订阅通常引入中间代理(如消息队列),而观察者模式是直接通信
- 与中介者模式:都用于减少对象间耦合,但中介者集中控制逻辑,而观察者保持对象独立性
6. 实战中的坑与最佳实践
6.1 我踩过的三个典型坑
通知顺序依赖:某些观察者假设自己会最后执行,结果因注册顺序变化导致bug
- 解:要么明确指定顺序,要么确保观察者逻辑独立
递归通知:观察者更新时又修改主题状态,导致无限递归
- 解:设置状态变更标志位,或在批量更新完成后统一通知
跨线程问题:主题和观察者运行在不同线程导致竞态条件
- 解:使用线程安全集合存储观察者,或采用CopyOnWriteArrayList
6.2 性能优化技巧
- 懒惰通知:高频变更时,可以积累变更并定期批量通知
- 差异化通知:只通知关注特定变化的观察者(如通过事件类型过滤)
- 观察者分组:对观察者进行分组,不同组别采用不同通知策略
6.3 测试策略
观察者模式的单元测试要点:
@Test public void testPriceChangeNotification() { // 创建mock观察者 Observer mockObserver = mock(Observer.class); Product product = new Product("Test", 100); product.registerObserver(mockObserver); // 触发状态变更 product.setPrice(90); // 验证观察者被调用 verify(mockObserver).update(90); }7. 现代语言中的原生支持
7.1 Java中的Observable类
Java原本提供了java.util.Observable类和Observer接口,但由于设计缺陷(如Observable是类而非接口),在Java 9中已被标记为废弃。现在更推荐自行实现或使用事件总线库。
7.2 C#中的事件机制
C#直接内置了事件语法糖:
class Product { public event EventHandler PriceChanged; private float price; public float Price { get { return price; } set { price = value; PriceChanged?.Invoke(this, EventArgs.Empty); } } }7.3 JavaScript中的观察者
前端开发中随处可见观察者模式:
// DOM事件 button.addEventListener('click', callback); // 自定义事件 const emitter = new EventEmitter(); emitter.on('priceChange', (newPrice) => { console.log(`Price updated to ${newPrice}`); });观察者模式之所以能历经数十年而不衰,正是因为它完美解决了对象间动态协作的根本需求。从GUI事件处理到微服务间通信,从简单的价格通知到复杂的响应式系统,这个模式的变体无处不在。理解它的核心思想后,你会发现在我们日常开发的各个角落,都闪烁着观察者模式智慧的光芒。