Java 设计模式之观察者模式(Observer Pattern):java-design-patterns 源码级实战解析 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载观察者模式Observer Pattern又称发布-订阅模式定义了一种一对多的对象依赖关系当一个对象主题 Subject的状态发生改变时所有依赖它的对象观察者 Observer都会被自动通知并更新。本文以 java-design-patterns 仓库中的 observer 模块为核心从真实世界例子、接口设计、核心实现、泛型进阶版本、单元测试到典型应用场景逐层剖析这一 GoF 行为型模式在 Java 中的完整落地方式读完后你将能够独立设计并实现一套松耦合、可复用的观察者体系。观察者模式类图模式概述目的与定义观察者模式的核心目的是定义一种一对多的对象依赖关系当一个对象改变状态时所有依赖它的对象都将自动被通知或更新。从维基百科的定义来看观察者模式是一种软件设计模式它有一个被称为主题Subject的对象维护着一个所有依赖它的对象清单也就是观察者清单。当主题的状态发生改变时主题通常会调用观察者的方法来自动通知观察者们。该模式在 java-design-patterns 仓库中归属于Behavioral行为型分类并带有Gang Of Four与Reactive两个标签这意味着它既属于经典四人组设计模式又是响应式编程思想的重要基石。在 observer 模块 中模式的核心参与者被拆解为四类角色本模块中的类/接口职责主题SubjectWeather持有状态维护观察者列表状态变更时广播通知观察者接口ObserverWeatherObserver定义统一的update通知契约具体观察者Concrete ObserverOrcs、Hobbits实现update对状态变更做出各自响应状态类型WeatherType封装可被观察的天气状态枚举真实世界例子与通俗理解在遥远的土地上生活着霍比特人和兽人的种族。他们都是户外生活的人所以他们密切关注天气的变化。可以说他们不断地关注着天气。通俗地说注册成为一个观察者以接收对象状态的改变。在这个故事中Weather天气是主题它持续变化Orcs兽人与Hobbits霍比特人是观察者他们预先订阅了天气变化一旦天气改变就自动获得通知而无需反复主动轮询查询。编程示例从接口到完整实现1. 观察者接口与具体观察者首先定义天气观察者的接口以及两个种族——兽人和霍比特人public interface WeatherObserver { void update(WeatherType currentWeather); } Slf4j public class Orcs implements WeatherObserver { Override public void update(WeatherType currentWeather) { LOGGER.info(The orcs are facing currentWeather.getDescription() weather now); } } Slf4j public class Hobbits implements WeatherObserver { Override public void update(WeatherType currentWeather) { LOGGER.info(The hobbits are facing {} weather now, currentWeather.getDescription()); } }从源码可以确认WeatherObserver 只声明了唯一的方法void update(WeatherType currentWeather)Orcs 与 Hobbits 分别实现该接口并使用 Lombok 的Slf4j注解生成日志记录器。两个观察者互不依赖、互不知道对方的存在这正是观察者模式解耦能力的直接体现。2. 状态类型枚举观察者接收的通知参数类型是 WeatherType 枚举它定义了四种天气状态并通过description字段携带人类可读的描述public enum WeatherType { SUNNY(Sunny), RAINY(Rainy), WINDY(Windy), COLD(Cold); Getter private final String description; WeatherType(String description) { this.description description; } Override public String toString() { return this.name().toLowerCase(); } }toString()返回小写名称如rainy而getDescription()返回首字母大写的描述文本如Rainy二者分别服务于日志输出与面向用户的展示。3. 主题不断变化的天气接下来是作为主题的Weather类Slf4j public class Weather { private WeatherType currentWeather; private final ListWeatherObserver observers; public Weather() { observers new ArrayList(); currentWeather WeatherType.SUNNY; } public void addObserver(WeatherObserver obs) { observers.add(obs); } public void removeObserver(WeatherObserver obs) { observers.remove(obs); } /** Makes time pass for weather. */ public void timePasses() { var enumValues WeatherType.values(); currentWeather enumValues[(currentWeather.ordinal() 1) % enumValues.length]; LOGGER.info(The weather changed to {}., currentWeather); notifyObservers(); } private void notifyObservers() { for (var obs : observers) { obs.update(currentWeather); } } }对照 Weather.java 源码可以提炼出主题的三项核心职责观察者管理addObserver/removeObserver负责注册与注销观察者状态推进timePasses()模拟时间流逝通过(currentWeather.ordinal() 1) % enumValues.length在四种天气间循环切换并打印The weather changed to ...日志广播通知私有方法notifyObservers()遍历观察者列表逐一调用obs.update(currentWeather)。主题对每个观察者的唯一认知就是WeatherObserver接口因此新增观察者类型完全不需要修改主题代码。注意构造函数将初始天气设为SUNNY这意味着第一次调用timePasses()后天气变为RAINY。4. 完整运行示例var weather new Weather(); weather.addObserver(new Orcs()); weather.addObserver(new Hobbits()); weather.timePasses(); // The weather changed to rainy. // The orcs are facing rainy weather now // The hobbits are facing rainy weather now weather.timePasses(); // The weather changed to windy. // The orcs are facing windy weather now // The hobbits are facing windy weather now weather.timePasses(); // The weather changed to cold. // The orcs are facing cold weather now // The hobbits are facing cold weather now weather.timePasses(); // The weather changed to sunny. // The orcs are facing sunny weather now // The hobbits are facing sunny weather now这段输出与 App.java 的入口逻辑 完全一致每次天气变化已注册的Orcs与Hobbits都会被自动通知并按注册顺序依次打印日志。四种天气各遍历一轮后回到起点SUNNY形成完整循环。泛型进阶版本基于 Java Generics and Collections 的实现仓库还在com.iluwatar.observer.generic子包中提供了一个泛型化的进阶实现灵感来自《Java Generics and Collections》Naftalin Wadler 著。它在保留全部语义的同时将主题与观察者抽象为可复用的泛型基类值得深入学习。泛型观察者接口public interface ObserverS extends ObservableS, O, A, O extends ObserverS, O, A, A { void update(S subject, A argument); }该接口通过三个类型参数建立了递归类型边界recursive type boundS是被观察的主题类型、O是观察者自身类型、A是通知携带的参数类型从而在编译期就约束了主题与观察者之间的配对关系。完整定义见 Observer.java。泛型主题基类public abstract class ObservableS extends ObservableS, O, A, O extends ObserverS, O, A, A { protected final ListO observers; public Observable() { this.observers new CopyOnWriteArrayList(); } public void addObserver(O observer) { this.observers.add(observer); } public void removeObserver(O observer) { this.observers.remove(observer); } SuppressWarnings(unchecked) public void notifyObservers(A argument) { for (var observer : observers) { observer.update((S) this, argument); } } }值得注意的是泛型版本选用 CopyOnWriteArrayList而普通版本使用ArrayList。这一差异意味着泛型版更适合多线程场景——遍历时使用不可变快照避免并发修改集合引发ConcurrentModificationException而普通版本在单线程演示中足够但并发环境下修改观察者列表时需要额外同步。从源码结构看普通版本更侧重于教学演示的简洁性泛型版本则兼顾了并发安全与类型安全。泛型主题与观察者的具体化Slf4j public class GenWeather extends ObservableGenWeather, Race, WeatherType { private WeatherType currentWeather; public GenWeather() { currentWeather WeatherType.SUNNY; } /** Makes time pass for weather. */ public void timePasses() { var enumValues WeatherType.values(); currentWeather enumValues[(currentWeather.ordinal() 1) % enumValues.length]; LOGGER.info(The weather changed to {}., currentWeather); notifyObservers(currentWeather); } }GenWeather 继承Observable并以GenWeather自身作为主题类型参数Race 是一个扩展了泛型Observer的标记接口public interface Race extends ObserverGenWeather, Race, WeatherType {}GenOrcs 实现Race其update(GenWeather weather, WeatherType weatherType)同时获得主题对象与状态参数Slf4j public class GenOrcs implements Race { Override public void update(GenWeather weather, WeatherType weatherType) { LOGGER.info(The orcs are facing weatherType.getDescription() weather now); } }对比两种实现可以看到普通版本的通知只携带WeatherType一个参数泛型版本的update则额外携带主题引用S subject观察者若需要可以反向查询主题的更多状态扩展性更强。运行泛型版本在 App.java 中泛型版本的调用方式与普通版本几乎相同印证了模式语义的一致性LOGGER.info(--Running generic version--); var genericWeather new GenWeather(); genericWeather.addObserver(new GenOrcs()); genericWeather.addObserver(new GenHobbits()); genericWeather.timePasses(); genericWeather.timePasses(); genericWeather.timePasses(); genericWeather.timePasses();源码级验证测试用例如何保障模式行为仓库为观察者模块提供了完整的单元测试是验证模式行为正确性的第一手证据。以 WeatherTest.java 为例1. 测试观察者的注册与注销testAddRemoveObserverfinal var observer mock(WeatherObserver.class); final var weather new Weather(); weather.addObserver(observer); verifyNoMoreInteractions(observer); weather.timePasses(); assertEquals(The weather changed to rainy., appender.getLastMessage()); verify(observer).update(WeatherType.RAINY); weather.removeObserver(observer); weather.timePasses(); assertEquals(The weather changed to windy., appender.getLastMessage()); verifyNoMoreInteractions(observer);该测试使用 Mockito 模拟观察者验证了三条关键行为注册后未发生状态变化时观察者不会收到任何通知timePasses()后观察者收到且仅收到一次update(WeatherType.RAINY)调用注销后即使天气再次变化观察者也不会再收到通知。2. 测试天气的循环推进testTimePassesfinal var inOrder inOrder(observer); final var weatherTypes WeatherType.values(); for (var i 1; i 20; i) { weather.timePasses(); inOrder.verify(observer).update(weatherTypes[i % weatherTypes.length]); }该测试连续调用 20 次timePasses()并用inOrder校验观察者收到的通知严格按WeatherType枚举顺序循环从实现上验证了(ordinal() 1) % length的状态推进算法。此外observer/src/test/java/com/iluwatar/observer/下还包含 AppTest.java、HobbitsTest.java、OrcsTest.java 以及 generic 包下的 GWeatherTest.java 等共同构成完整的测试矩阵。若需在本地运行这些测试可在仓库根目录执行 Maven 相关命令仓库已内置mvnw与mvnw.cmd包装脚本。类图结构上图observer.png清晰展示了本模块的静态结构Weather是主题类持有currentWeather与WeatherType关联与observers观察者集合提供addObserver/removeObserver/timePasses/notifyObservers方法WeatherObserver是观察者接口仅声明update(WeatherType)Hobbits与Orcs均实现WeatherObserver各自在update中输出不同日志WeatherType枚举提供SUNNY、RAINY、WINDY、COLD四种状态。仓库 etc 目录 还提供了时序图 observer-sequence-diagram.png、泛型版本类图 observer_with_generics.png 以及对应的 PlantUML 源文件 observer.urm.puml方便读者用源码生成自己的可视化图表。适用场景与典型用例根据文档定义在以下任意一种情况下都可以使用观察者模式抽象具有两个方面且一个方面依赖于另一个方面时将这些方面封装在单独的对象中可以让你分别进行更改和重用当一个对象的改变需要同时改变其他对象而你又不知道具体有多少对象需要改变时当一个对象可以通知其他对象而无需假设这些对象是谁时——换句话说你不想让这些对象紧耦合。典型用例一个对象的改变导致其他对象的改变。观察者模式在 Java 生态中的经典实例观察者模式在 Java 生态中无处不在文档列举了以下代表性实现java.util.ObserverJDK 早期提供的观察者接口配合java.util.Observable类使用是观察者模式在标准库中最经典的体现java.util.EventListenerJava 事件监听机制的基础接口Swing/AWT 等 GUI 工具包的全部事件模型都建立在其上javax.servlet.http.HttpSessionBindingListenerServlet 规范中用于监听 HttpSession 中对象绑定/解绑事件的接口RxJava响应式编程库将观察者模式与函数式流式处理深度结合。从 App.java 的类注释 还可以看到观察者模式同时是Model-View-ControllerMVC架构模式的关键组成部分——视图作为观察者订阅模型状态变化模型变化时自动刷新视图这正是绝大多数 GUI 工具包和前端框架的事件驱动基础。小结观察者模式通过主题维护观察者清单、状态变更时自动广播的机制实现了对象间的一对多松耦合通知。本仓库的 observer 模块给出了两种层次分明的实现基础版本以 Weather 与 WeatherObserver 为核心简洁直观适合作为学习模式的入门样例泛型进阶版本以 Observable 与 Observer 为基座通过递归类型边界获得编译期类型安全并借助CopyOnWriteArrayList提升并发安全性适合作为生产级代码的参考范式。配套的完整测试WeatherTest.java 等则为模式行为提供了可验证的保障。无论是事件驱动架构、GUI 事件模型还是响应式编程观察者模式都是你需要熟练掌握的第一块基石。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 设计模式之 Observer观察者模式在 java-design-patterns 仓库中掌握响应式事件通知Java 设计模式之 Observer观察者模式在 java design patterns 仓库中掌握响应式事件通知 本文以 java design p示例工程教程Unity3DTraining 设计模式实战观察者模式Observer PatternC 完整实现与源码解析Unity3DTraining 设计模式实战观察者模式Observer PatternC 完整实现与源码解析 导读 本文围绕 DesignPatterns示例工程Java 设计模式之 Observer观察者以 java-design-patterns 天气系统为例掌握一对多事件通知Java 设计模式之 Observer观察者以 java design patterns 天气系统为例掌握一对多事件通知 观察者Observer模式是示例工程教程上一篇CANN ops-nn 的 aclnnForeachAddListV2 算子接口详解Tensor 列表带系数加法与两段式调用实战下一篇Google AI Edge Gallery 实用上手指南在手机上离线跑本地 AI 模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考