Java设计模式:观察者模式从入门到精通
1. 引言什么是观察者模式观察者模式Observer Pattern是一种行为型设计模式它定义了对象之间的一种一对多的依赖关系。当一个对象称为“主题”或“被观察者”的状态发生改变时所有依赖于它的对象称为“观察者”都会自动收到通知并更新。核心思想解耦主题与观察者让它们可以独立变化同时保持通信。现实世界类比报纸订阅报社主题发布新报纸所有订阅者观察者都会收到。微信公众号公众号主题发布新文章所有关注者观察者都会在订阅号列表看到更新。在Java中观察者模式被广泛应用于事件监听、消息队列、MVC架构等场景。2. 模式结构与角色观察者模式主要包含四个角色Subject主题/被观察者维护一个观察者列表List。提供注册attach和注销detach观察者的方法。提供通知所有观察者的方法notifyObservers。ConcreteSubject具体主题继承或实现Subject。维护自身的具体状态state。当状态改变时调用父类的通知方法。Observer观察者接口定义一个更新方法例如update()供主题在通知时调用。ConcreteObserver具体观察者实现Observer接口。维护一个对ConcreteSubject的引用可选用于获取更详细的状态。实现update方法定义收到通知后具体的响应逻辑。UML类图示意---------------- ------------------- | Subject | | Observer | |----------------| |-------------------| | attach(Observer)|---| update() | | detach(Observer)| ------------------- | notifyObservers()| ^ ---------------- | ^ | | | ---------------- ------------------- | ConcreteSubject| | ConcreteObserver | |----------------| |-------------------| | -state | | -subject | | getState() | | update() | | setState() | ------------------- ----------------3. 从零实现经典观察者模式下面我们用一个简单的例子来演示一个气象站WeatherStation作为主题多个显示设备如CurrentConditionsDisplay、StatisticsDisplay作为观察者。3.1 定义观察者接口和主题接口// 观察者接口publicinterfaceObserver{voidupdate(floattemperature,floathumidity,floatpressure);}// 主题接口publicinterfaceSubject{voidregisterObserver(Observero);voidremoveObserver(Observero);voidnotifyObservers();}3.2 实现具体主题气象站importjava.util.ArrayList;importjava.util.List;// 具体主题气象站publicclassWeatherStationimplementsSubject{privateListObserverobservers;privatefloattemperature;privatefloathumidity;privatefloatpressure;publicWeatherStation(){observersnewArrayList();}OverridepublicvoidregisterObserver(Observero){observers.add(o);}OverridepublicvoidremoveObserver(Observero){observers.remove(o);}OverridepublicvoidnotifyObservers(){for(Observerobserver:observers){observer.update(temperature,humidity,pressure);}}// 气象数据更新方法publicvoidsetMeasurements(floattemperature,floathumidity,floatpressure){this.temperaturetemperature;this.humidityhumidity;this.pressurepressure;measurementsChanged();// 数据变化通知观察者}privatevoidmeasurementsChanged(){notifyObservers();}// 其他getter方法...}3.3 实现具体观察者显示设备// 具体观察者当前状况显示publicclassCurrentConditionsDisplayimplementsObserver{privatefloattemperature;privatefloathumidity;publicCurrentConditionsDisplay(SubjectweatherStation){weatherStation.registerObserver(this);// 注册自己}Overridepublicvoidupdate(floattemperature,floathumidity,floatpressure){this.temperaturetemperature;this.humidityhumidity;display();}publicvoiddisplay(){System.out.println(当前状况温度 temperature°C, 湿度 humidity%);}}// 具体观察者统计显示publicclassStatisticsDisplayimplementsObserver{privatefloatmaxTempFloat.MIN_VALUE;privatefloatminTempFloat.MAX_VALUE;privatefloattempSum0.0f;privateintnumReadings0;publicStatisticsDisplay(SubjectweatherStation){weatherStation.registerObserver(this);}Overridepublicvoidupdate(floattemperature,floathumidity,floatpressure){tempSumtemperature;numReadings;if(temperaturemaxTemp){maxTemptemperature;}if(temperatureminTemp){minTemptemperature;}display();}publicvoiddisplay(){System.out.println(温度统计平均 (tempSum/numReadings)°C, 最高 maxTemp°C, 最低 minTemp°C);}}3.4 客户端测试publicclassWeatherStationDemo{publicstaticvoidmain(String[]args){WeatherStationweatherStationnewWeatherStation();CurrentConditionsDisplaycurrentDisplaynewCurrentConditionsDisplay(weatherStation);StatisticsDisplaystatisticsDisplaynewStatisticsDisplay(weatherStation);// 模拟气象数据更新System.out.println(第一次测量);weatherStation.setMeasurements(25.0f,65.0f,1013.0f);System.out.println(\n第二次测量);weatherStation.setMeasurements(26.5f,70.0f,1012.5f);System.out.println(\n第三次测量);weatherStation.setMeasurements(24.0f,80.0f,1014.0f);}}输出结果第一次测量 当前状况温度 25.0°C, 湿度 65.0% 温度统计平均 25.0°C, 最高 25.0°C, 最低 25.0°C 第二次测量 当前状况温度 26.5°C, 湿度 70.0% 温度统计平均 25.75°C, 最高 26.5°C, 最低 25.0°C 第三次测量 当前状况温度 24.0°C, 湿度 80.0% 温度统计平均 25.166666°C, 最高 26.5°C, 最低 24.0°C4. Java内置支持java.util.Observable与java.util.ObserverJava在早期版本中提供了对观察者模式的内置支持java.util.Observable类 和java.util.Observer接口但在Java 9中被标记为Deprecated主要原因是Observable是一个类而非接口限制了继承灵活性。方法setChanged()是protected的导致通知控制不够直观。没有提供传递事件对象Event Object的泛型支持。了解历史实现已过时仅作了解importjava.util.Observable;importjava.util.Observer;// 被观察者继承ObservableclassOldWeatherStationextendsObservable{privatefloattemperature;publicvoidsetTemperature(floattemperature){this.temperaturetemperature;setChanged();// 标记状态已改变notifyObservers(temperature);// 通知观察者可传递数据}}// 观察者实现ObserverclassOldDisplayimplementsObserver{Overridepublicvoidupdate(Observableo,Objectarg){if(arginstanceofFloat){System.out.println(温度更新为: arg);}}}建议在现代Java开发中建议使用自定义接口如第3节所示或更强大的事件驱动框架如Spring ApplicationEvent、Guava EventBus等而非java.util.Observable。5. 进阶实战推模型 vs 拉模型观察者模式有两种主要的数据传递方式5.1 推模型Push Model主题将数据“推”给观察者。观察者的update方法接收主题传递的全部或部分数据如我们之前的例子。优点观察者无需主动查询主题状态。缺点可能传递观察者不需要的数据不够灵活。// 推模型示例update方法接收具体数据voidupdate(floattemp,floathumidity,floatpressure);5.2 拉模型Pull Model主题只通知观察者“有变化”观察者根据需要主动从主题“拉取”数据。观察者的update方法可能只接收主题引用观察者再调用主题的getter方法获取所需数据。优点观察者可以按需获取数据更灵活。缺点观察者需要了解主题接口耦合度略有增加。// 拉模型示例update方法接收主题引用publicinterfaceObserver{voidupdate(Subjectsubject);// 观察者通过subject.getState()拉取数据}publicclassConcreteObserverimplementsObserver{Overridepublicvoidupdate(Subjectsubject){if(subjectinstanceofWeatherStation){WeatherStationws(WeatherStation)subject;floattempws.getTemperature();// ... 使用temp}}}实际应用通常采用推拉结合的方式主题将关键变更数据作为事件对象推送观察者如需更多细节再主动拉取。6. 观察者模式在Spring框架中的应用Spring框架的事件机制是观察者模式的经典工业级实现。6.1 定义应用事件importorg.springframework.context.ApplicationEvent;// 自定义事件订单创建事件publicclassOrderCreatedEventextendsApplicationEvent{privateStringorderId;privateBigDecimalamount;publicOrderCreatedEvent(Objectsource,StringorderId,BigDecimalamount){super(source);this.orderIdorderId;this.amountamount;}// getters...}6.2 发布事件主题importorg.springframework.context.ApplicationEventPublisher;importorg.springframework.stereotype.Service;ServicepublicclassOrderService{privatefinalApplicationEventPublisherpublisher;publicOrderService(ApplicationEventPublisherpublisher){this.publisherpublisher;}publicvoidcreateOrder(StringorderId,BigDecimalamount){// 创建订单逻辑...System.out.println(订单创建: orderId);// 发布事件publisher.publishEvent(newOrderCreatedEvent(this,orderId,amount));}}6.3 监听事件观察者importorg.springframework.context.event.EventListener;importorg.springframework.stereotype.Component;ComponentpublicclassNotificationService{EventListenerpublicvoidhandleOrderCreated(OrderCreatedEventevent){System.out.println(发送短信通知: 订单 event.getOrderId() 已创建金额 event.getAmount());}}ComponentpublicclassInventoryService{EventListenerpublicvoidupdateInventory(OrderCreatedEventevent){System.out.println(更新库存: 为订单 event.getOrderId() 预留库存);}}优势完全解耦订单服务不需要知道有哪些监听器。易于扩展新增监听器只需添加一个带有EventListener的方法。支持异步通过Async注解可实现异步事件处理。7. 模式优缺点与适用场景7.1 优点开闭原则无需修改主题即可新增观察者。松耦合主题和观察者之间抽象耦合各自可以独立变化和复用。支持广播通信主题一次通知所有观察者都能收到。7.2 缺点通知顺序不确定观察者被通知的顺序可能不可控依赖注册顺序或实现细节。可能引起性能问题如果观察者数量巨大或update方法执行缓慢通知过程可能成为瓶颈。循环依赖风险如果观察者在update方法中又修改了主题状态可能导致无限递归。7.3 适用场景事件驱动系统GUI事件处理、消息中间件。发布-订阅模型消息队列、事件总线。MVC架构模型Model变化时自动更新视图View。监控与报警系统监控指标变化触发报警。工作流引擎一个任务完成触发后续任务。8. 与其他模式的关系与发布-订阅模式观察者模式通常是一对多、同步的发布-订阅模式通常引入中介消息代理支持多对多、异步解耦更彻底。与中介者模式中介者模式集中管理对象间的通信观察者模式中主题和观察者直接通信但通过接口解耦。与责任链模式责任链模式中请求沿链传递观察者模式中通知是广播给所有观察者。9. 最佳实践与陷阱规避9.1 最佳实践使用事件对象将变更数据封装成事件对象传递避免修改update方法签名。考虑线程安全如果主题可能在多线程环境下被修改确保观察者列表的线程安全如使用CopyOnWriteArrayList。防止内存泄漏及时注销不再需要的观察者特别是观察者生命周期短于主题时。支持异步通知对于耗时观察者考虑使用线程池异步执行update方法。9.2 常见陷阱在update方法中修改主题可能导致递归通知或状态不一致。忽略异常处理某个观察者update方法抛出异常不应影响其他观察者。过度通知频繁的状态微变化可能导致观察者被“轰炸”考虑添加防抖debounce或节流throttle机制。10. 总结观察者模式是构建松耦合、事件驱动系统的基石。从最简单的自定义实现到Spring框架的ApplicationEvent再到分布式消息队列其核心思想贯穿始终。掌握要点理解主题与观察者的角色划分。熟练实现推模型和拉模型。了解Java内置支持的局限性。掌握在Spring等现代框架中的应用。注意线程安全、内存泄漏等实践细节。通过本教程你已从零实现了观察者模式了解了其进阶用法和工业级实践。接下来尝试在你自己的项目中应用它例如构建一个简单的用户行为追踪系统或者改造现有的回调逻辑为观察者模式体会其带来的解耦优势。