观察者模式

张开发
2026/5/31 6:25:03 15 分钟阅读
观察者模式
一、定义观察者模式Observer Pattern是行为型设计模式的核心成员其核心思想是建立“一对多”的对象依赖关系当被观察对象主题状态发生变更时所有依赖它的观察者会被自动通知并更新。这种模式能彻底解耦主题与观察者让两者独立扩展是实现“事件驱动”“发布-订阅”机制的经典方案。无论是GUI交互、消息通知还是数据同步观察者模式都有着广泛的应用。二、核心原理观察者模式包含两个核心角色以及一套规范的交互流程确保主题与观察者的解耦通信。1.核心角色被观察者Subject/Topic也称为主题是状态变更的源头。它维护一个观察者列表提供注册、移除观察者的方法以及状态变更时通知所有观察者的核心方法。观察者Observer依赖主题的对象需实现统一的更新接口。当收到主题的通知时通过该接口响应状态变更执行自身业务逻辑。2.交互流程观察者通过主题的“注册”方法加入主题的观察者列表建立依赖关系主题内部状态发生变更时主动调用自身的“通知”方法通知方法遍历观察者列表依次调用每个观察者的“更新”接口传递状态信息观察者通过更新接口接收状态执行对应逻辑如刷新界面、处理数据观察者可通过“移除”方法退出列表不再接收主题通知。三、典型应用场景观察者模式适用于“一个对象变更需联动多个对象响应”的场景以下是行业内的典型应用1.GUI事件监听桌面应用或Web前端中按钮点击、文本框输入等事件均基于观察者模式实现。例如按钮主题被点击时注册在按钮上的回调函数观察者会被触发执行弹窗、页面跳转等操作。Java Swing中的ActionListener、JavaScript中的addEventListener本质都是观察者模式的封装。2.消息通知系统社交平台、即时通讯工具的消息推送功能用户观察者订阅话题/好友主题后当主题发布新内容所有订阅者会收到消息通知。此外系统告警、邮件推送等场景也常用此模式确保告警信息能同步触达多个监控模块。3.数据订阅-发布金融系统中股票价格主题变动时行情面板、交易策略、风控模块多个观察者需实时同步更新物联网系统中传感器数据主题变化后监控终端、数据存储模块、告警模块会联动响应确保数据一致性。4.状态监控与同步后台服务集群中主节点主题状态变化如宕机、重启时从节点、负载均衡器、监控平台观察者会收到通知执行故障转移、重新路由等操作保障系统可用性。四、Java实现外部类与内部类双版本Java天然支持观察者模式可通过接口定义角色规范。以下分别实现外部类解耦性强和内部类访问便捷版本均包含完整可运行代码。1. 外部类实现推荐高复用性主题与观察者均为独立类通过接口关联适合观察者需跨场景复用的场景。// 1. 观察者接口统一更新规范 public interface Observer { // 接收主题通知的更新方法data为主题传递的状态数据 void update(String data); } // 2. 被观察者主题类 public class Subject { // 维护观察者列表 private ListObserver observerList new ArrayList(); // 主题内部状态 private String state; // 注册观察者 public void attach(Observer observer) { if (observer ! null !observerList.contains(observer)) { observerList.add(observer); } } // 移除观察者 public void detach(Observer observer) { observerList.remove(observer); } // 通知所有观察者 private void notifyObservers() { for (Observer observer : observerList) { observer.update(state); // 调用观察者更新方法 } } // 对外提供修改状态的方法状态变更后自动通知 public void setState(String state) { this.state state; notifyObservers(); // 状态变更触发通知 } // 获取当前状态可选供观察者主动获取 public String getState() { return state; } } // 3. 具体观察者1例如消息弹窗模块 public class PopupObserver implements Observer { Override public void update(String data) { System.out.println(弹窗模块收到通知 data); // 业务逻辑弹出消息提示框 } } // 4. 具体观察者2例如日志记录模块 public class LogObserver implements Observer { Override public void update(String data) { System.out.println(日志模块记录 LocalDateTime.now() - data); // 业务逻辑将消息写入日志文件 } } // 5. 客户端测试 public class Client { public static void main(String[] args) { // 创建主题 Subject subject new Subject(); // 创建观察者 Observer popup new PopupObserver(); Observer log new LogObserver(); // 注册观察者 subject.attach(popup); subject.attach(log); // 修改主题状态触发通知 subject.setState(新消息Hello Observer Pattern); // 输出结果 // 弹窗模块收到通知新消息Hello Observer Pattern // 日志模块记录2026-01-20T15:30:00 - 新消息Hello Observer Pattern // 移除弹窗观察者 subject.detach(popup); // 再次修改状态 subject.setState(消息已读); // 输出结果 // 日志模块记录2026-01-20T15:30:05 - 消息已读 } }2.内部类实现紧凑性强仅服务于当前主题观察者作为主题的内部类可直接访问主题的私有成员无需通过getter传递状态适合观察者仅与当前主题关联、无需复用的场景。// 主题类内部包含观察者接口与具体观察者 public class SubjectWithInner { // 主题内部状态 private String state; // 维护观察者列表泛型为内部观察者接口 private ListInnerObservergt; observerList new ArrayList(); // 1. 内部观察者接口仅主题内部可用 public interface InnerObserver { void update(); } // 2. 内部具体观察者1弹窗模块 public class PopupInnerObserver implements InnerObserver { Override public void update() { // 直接访问主题私有状态state System.out.println(内部弹窗模块收到通知 state); } } // 3. 内部具体观察者2日志模块 public class LogInnerObserver implements InnerObserver { Override public void update() { System.out.println(内部日志模块记录 LocalDateTime.now() - state); } } // 注册观察者接收内部观察者实例 public void attach(InnerObserver observer) { if (observer ! null !observerList.contains(observer)) { observerList.add(observer); } } // 移除观察者 public void detach(InnerObserver observer) { observerList.remove(observer); } // 通知所有内部观察者 private void notifyObservers() { for (InnerObserver observer : observerList) { observer.update(); } } // 修改状态并通知 public void setState(String state) { this.state state; notifyObservers(); } } // 客户端测试 public class InnerClient { public static void main(String[] args) { // 创建主题实例 SubjectWithInner subject new SubjectWithInner(); // 创建内部观察者需通过主题实例获取 SubjectWithInner.PopupInnerObserver popup subject.new PopupInnerObserver(); SubjectWithInner.LogInnerObserver log subject.new LogInnerObserver(); // 注册观察者 subject.attach(popup); subject.attach(log); // 修改状态触发通知 subject.setState(内部类实现状态更新); // 输出结果 // 内部弹窗模块收到通知内部类实现状态更新 // 内部日志模块记录2026-01-20T15:35:00 - 内部类实现状态更新 } }五、C实现外部类和内部类双版本C通过抽象类定义观察者接口内部类可采用“嵌套类”实现注意C嵌套类默认不持有外部类指针需显式传递以访问外部类成员。1. 外部类实现跨模块复用#include iostream #include vector #include string #include ctime using namespace std; // 1. 观察者抽象类接口 class Observer { public: // 纯虚函数定义更新接口 virtual void update(const string data) 0; // 虚析构函数确保子类析构正常 virtual ~Observer() {} }; // 2. 被观察者主题类 class Subject { private: vectorObserver* observerList; // 观察者列表 string state; // 内部状态 public: // 注册观察者 void attach(Observer* observer) { if (observer ! nullptr) { observerList.push_back(observer); } } // 移除观察者 void detach(Observer* observer) { for (auto it observerList.begin(); it ! observerList.end(); it) { if (*it observer) { observerList.erase(it); break; } } } // 通知所有观察者 void notifyObservers() { for (Observer* observer : observerList) { observer-update(state); } } // 设置状态并触发通知 void setState(const string state) { this-state state; notifyObservers(); } // 获取状态可选 string getState() const { return state; } }; // 3. 具体观察者1弹窗模块 class PopupObserver : public Observer { public: void update(const string data) override { cout 弹窗模块收到通知 data endl; } }; // 4. 具体观察者2日志模块 class LogObserver : public Observer { public: void update(const string data) override { // 获取当前时间 time_t now time(nullptr); char timeBuf[64]; strftime(timeBuf, sizeof(timeBuf), %Y-%m-%d %H:%M:%S, localtime(now)); cout 日志模块记录 timeBuf - data endl; } }; // 客户端测试 int main() { Subject* subject new Subject(); Observer* popup new PopupObserver(); Observer* log new LogObserver(); // 注册观察者 subject-attach(popup); subject-attach(log); // 触发状态更新 subject-setState(C外部类新消息到来); // 输出结果 // 弹窗模块收到通知C外部类新消息到来 // 日志模块记录2026-01-20 15:40:00 - C外部类新消息到来 // 移除观察者 subject-detach(popup); subject-setState(消息已处理); // 输出结果 // 日志模块记录2026-01-20 15:40:05 - 消息已处理 // 释放内存 delete log; delete popup; delete subject; return 0; }2. 内部类实现嵌套类方式C嵌套类需显式持有外部主题类的指针才能访问外部类的私有成员实现逻辑与Java内部类略有差异。#include iostream #include vector #include string #include ctime using namespace std; // 主题类包含嵌套观察者类 class SubjectWithInner { private: string state; // 外部类私有状态 // 观察者列表存储嵌套类指针 vectorSubjectWithInner::InnerObserver* observerList; public: // 1. 嵌套观察者抽象类内部接口 class InnerObserver { protected: SubjectWithInner* outerSubject; // 持有外部类指针用于访问外部状态 public: // 构造函数传递外部类实例 InnerObserver(SubjectWithInner* outer) : outerSubject(outer) {} virtual void update() 0; virtual ~InnerObserver() {} }; // 2. 嵌套具体观察者1弹窗模块 class PopupInnerObserver : public InnerObserver { public: PopupInnerObserver(SubjectWithInner* outer) : InnerObserver(outer) {} void update() override { // 通过外部类指针访问私有状态 cout 内部弹窗模块收到通知 outerSubject-state endl; } }; // 3. 嵌套具体观察者2日志模块 class LogInnerObserver : public InnerObserver { public: LogInnerObserver(SubjectWithInner* outer) : InnerObserver(outer) {} void update() override { time_t now time(nullptr); char timeBuf[64]; strftime(timeBuf, sizeof(timeBuf), %Y-%m-%d %H:%M:%S, localtime(now)); cout 内部日志模块记录 timeBuf - outerSubject-state endl; } }; // 注册嵌套观察者 void attach(InnerObserver* observer) { if (observer ! nullptr) { observerList.push_back(observer); } } // 移除嵌套观察者 void detach(InnerObserver* observer) { for (auto it observerList.begin(); it ! observerList.end(); it) { if (*it observer) { observerList.erase(it); break; } } } // 通知所有嵌套观察者 void notifyObservers() { for (InnerObserver* observer : observerList) { observer-update(); } } // 设置状态并通知 void setState(const string state) { this-state state; notifyObservers(); } }; // 客户端测试 int main() { SubjectWithInner* subject new SubjectWithInner(); // 创建嵌套观察者传递外部类实例 SubjectWithInner::PopupInnerObserver* popup new SubjectWithInner::PopupInnerObserver(subject); SubjectWithInner::LogInnerObserver* log new SubjectWithInner::LogInnerObserver(subject); // 注册观察者 subject-attach(popup); subject-attach(log); // 触发状态更新 subject-setState(C内部类状态变更); // 输出结果 // 内部弹窗模块收到通知C内部类状态变更 // 内部日志模块记录2026-01-20 15:45:00 - C内部类状态变更 // 释放内存 delete log; delete popup; delete subject; return 0; }六、两种实现方式对比实现方式优点缺点适用场景外部类解耦性强观察者可跨主题复用代码结构清晰便于维护需通过接口/ getter传递状态略增加代码量观察者需复用、多主题共享观察者、大型项目模块化开发内部类访问外部类成员便捷无需额外传参代码紧凑与主题强关联观察者无法复用耦合度高Java中需通过外部类实例创建观察者仅服务于单个主题、逻辑简单、无需复用的场景七、使用注意事项1.避免循环依赖观察者与主题相互持有引用时易导致内存泄漏如Java中的强引用、C中的裸指针。解决方案Java使用弱引用存储观察者C使用智能指针shared_ptr或在观察者销毁前主动移除注册。2.线程安全问题多线程环境下主题通知观察者、观察者注册/移除时需加锁如Java的synchronized、C的mutex避免并发修改观察者列表导致异常。3.通知顺序控制默认情况下主题按注册顺序通知观察者。若业务需指定通知优先级可将观察者列表改为有序结构如优先级队列或自定义排序逻辑。4.避免过度通知主题状态频繁变更时会频繁触发观察者更新导致性能损耗。可通过“状态防抖”合并连续变更、“懒通知”延迟到必要时通知优化。八、总结观察者模式的核心价值的是“解耦主题与观察者实现联动更新”其设计思想贯穿于各类事件驱动、发布-订阅场景。外部类实现适合追求复用与解耦的大型项目内部类实现适合简单场景的紧凑编码开发者需根据业务需求选择合适的实现方式。需要注意的是观察者模式并非“银弹”对于仅需单次联动、逻辑简单的场景直接调用方法可能比引入模式更高效。只有当联动关系复杂、需频繁扩展观察者时引入观察者模式才能真正提升代码的可维护性与扩展性。

更多文章