观察者模式
1. 观察者模式 (Observer Pattern)
代码示例
#include <iostream>
#include <vector>
#include <algorithm> // for std::remove
#include <string>
using namespace std;
// 1. 抽象观察者 (Interface)
// 稳定点:所有想要接收通知的对象,必须遵守这个契约
class IObserver {
public:
virtual ~IObserver() {}
// 核心接口:被通知时调用的方法
virtual void Update(const string& message) = 0;
};
// 2. 目标/主题 (Subject)
// 负责维护观察者列表,并发送通知
class DataCenter {
private:
// 【组合 (Composition)】
// 核心:使用容器存储抽象观察者的指针。
// 这对应了“一对多”关系中的“多”。
vector<IObserver*> observerList;
string msg; // 核心数据(状态)
public:
// 【管理接口】应对“多”的增加
void Attach(IObserver* obs) {
observerList.push_back(obs);
}
// 【管理接口】应对“多”的减少
void Detach(IObserver* obs) {
// C++ remove idiom: 从 vector 中移除指定指针
observerList.erase(std::remove(observerList.begin(), observerList.end(), obs), observerList.end());
}
// 【通知机制】
// 稳定点:遍历列表并调用 Update 的逻辑是固定的
void Notify() {
cout << "[DataCenter] 广播通知中..." << endl;
for (auto obs : observerList) {
obs->Update(msg); // 多态调用
}
}
// 业务逻辑:数据变了,触发通知
void SetMsg(string newMsg) {
this->msg = newMsg;
cout << "\n>>> 数据发生变更: " << newMsg << endl;
Notify();
}
};
// -----------------------------------------------------------
// 3. 具体观察者 A:控制台屏幕
class ConsoleScreen : public IObserver {
public:
void Update(const string& message) override {
cout << " -> [屏幕] 显示内容: " << message << endl;
}
};
// 4. 具体观察者 B:日志系统
class Logger : public IObserver {
public:
void Update(const string& message) override {
cout << " -> [日志] 记录文件: " << message << endl;
}
};
// 5. 具体观察者 C:短信报警 (扩展演示)
class SMSAlert : public IObserver {
public:
void Update(const string& message) override {
cout << " -> [短信] 发送报警给管理员: " << message << endl;
}
};
// -----------------------------------------------------------
int main() {
DataCenter dc;
// 创建观察者
ConsoleScreen screen;
Logger logger;
SMSAlert sms;
cout << "=== 场景 1: 初始化 (屏幕 + 日志) ===" << endl;
dc.Attach(&screen);
dc.Attach(&logger);
dc.SetMsg("服务器 CPU 占用 50%");
cout << "=== 场景 2: 动态扩展 (增加短信报警) ===" << endl;
dc.Attach(&sms);
dc.SetMsg("服务器 CPU 占用 99% (危!)");
cout << "=== 场景 3: 动态缩减 (日志系统下线) ===" << endl;
dc.Detach(&logger);
dc.SetMsg("服务器恢复正常");
return 0;
}
1. 定义
- 原文: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 解读:
- “一” (Subject): 指的是
DataCenter。它是数据的源头。 - “多” (Observer): 指的是
Screen,Logger,SMS。它们是数据的消费者。 - “自动更新”: Subject 不需要知道 Observer 具体要干什么,它只管调
Update(),剩下的事 Observer 自己搞定。
- “一” (Subject): 指的是
- 核心价值: 解除耦合。让 Subject 和 Observer 可以独立演化,互不影响。
2. 解决问题 (Stable vs. Changing)
这也是“分离变与不变”的经典案例。
- 稳定点 (Stable):通知机制
- “当数据变化时,需要通知所有注册者” —— 这个逻辑是永恒不变的。
- 不管观察者是谁,
Notify()函数里的for循环遍历逻辑不需要改动。
- 变化点 (Changing):观察者的集合
- 观察者的数量是变化的(随时
Attach/Detach)。 - 观察者的具体类型是变化的(今天是屏幕,明天可能是手机 App)。
- 观察者的数量是变化的(随时
3. 代码结构 (Code Structure)
实现该模式的三个关键支柱:
- 面向抽象编程:
DataCenter内部持有的不是ConsoleScreen*,而是vector<IObserver*>。- 这使得 DataCenter 对具体的观察者一无所知,只知道它们实现了
Update接口。
- 动态组合 (Attach/Detach):
- 这是观察者模式区别于模板方法模式的地方。模板方法是静态的继承关系,而观察者模式是动态的聚合关系。
- 你可以在程序运行时(Runtime)随意插拔观察者。
- 触发机制 (SetMsg -> Notify):
- 在状态改变的函数(如
SetMsg)末尾,必须显式调用Notify()来驱动整个链条。
- 在状态改变的函数(如
4. 符合哪些设计原则?
- A. 依赖倒置原则 (DIP) —— 灵魂所在
- 高层模块 (
DataCenter) 不依赖 低层模块 (Logger,Screen)。 - 两者都依赖于 抽象 (
IObserver)。 - 如果
DataCenter直接 include 了Logger.h,那就完了,耦合度爆炸。现在它只依赖IObserver.h。
- 高层模块 (
- B. 开闭原则 (OCP)
- 对扩展开放: 如果你要加一个“微信通知”,只需要写一个
class Wechat : public IObserver,然后在main里Attach进去。 - 对修改关闭: 你完全不需要修改
DataCenter类的任何一行代码(不需要改动Notify循环)。
- 对扩展开放: 如果你要加一个“微信通知”,只需要写一个
- C. 合成复用原则 (CRP)
- 多用组合,少用继承。
DataCenter和IObserver之间是组合 (Has-A) 关系。这比继承更加灵活,解耦更加彻底。
- D. 单一职责原则 (SRP)
DataCenter:只负责维护数据和分发通知,不负责具体的显示逻辑。Observer:只负责接收数据并处理展示,不负责维护数据源。
5. 如何扩展?
这是检验你是否掌握模式的标准:
- 实现抽象接口:
class NewDevice : public IObserver
- 实现 Update 方法:
override Update(const string& msg) { ... }
- 动态注册:
- 在客户端代码中调用
dc.Attach(newDevicePtr);
- 在客户端代码中调用
注意: 整个扩展过程中,Subject (DataCenter) 的源代码是“只读”的,不需要做任何修改。这就是设计模式带来的架构红利。
2.拓展
如何利用开闭原则
- 应对稳定点 -> 抽象: 把不通过变的东西抽离出来,写成接口或基类。
- 应对变化点 -> 扩展(继承和组合): 把会变的东西通过继承子类或组合对象的方式注入进去。
结合代码中的 class DataCenter(数据中心),这很明显是要实现一个观察者模式(Observer Pattern)。数据中心是核心数据源(稳定),而谁来观察数据、怎么展示数据是多变的(变化)。
1. "应对稳定点,抽象"
- 对应的代码:
IObserver接口 和DataCenter::Notify()方法。 - 解释: 无论你的业务怎么变,“当数据改变时需要通知一群人”这个机制是稳定的。所以我们将“通知”这个行为抽象成
Update()接口,将“广播”这个行为写死在Notify()里。
2. "应对变化点,扩展(继承和组合)"
这行注释提到了两个核心手段:
- 继承 (Inheritance):
- 体现:
MonitorScreen和Logger继承IObserver。 - 作用: 用来定义新的行为。如果你想要一个新的报警方式(比如发邮件),通过继承扩展出一个
EmailAlert类即可。
- 体现:
- 组合 (Composition):
- 体现:
DataCenter中持有vector<IObserver*> observerList。 - 作用: 用来动态组装对象。DataCenter 不知道具体是谁在观察它,它只是“持有”一堆指针。这使得我们可以在程序运行时(Runtime)随意地
Attach(插拔)观察者,而不需要重新编译DataCenter的代码。
- 体现:
浙公网安备 33010602011771号