观察者模式

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 和 Observer 可以独立演化,互不影响。

2. 解决问题 (Stable vs. Changing)

这也是“分离变与不变”的经典案例。

  • 稳定点 (Stable):通知机制
    • “当数据变化时,需要通知所有注册者” —— 这个逻辑是永恒不变的。
    • 不管观察者是谁,Notify() 函数里的 for 循环遍历逻辑不需要改动。
  • 变化点 (Changing):观察者的集合
    • 观察者的数量是变化的(随时 Attach/Detach)。
    • 观察者的具体类型是变化的(今天是屏幕,明天可能是手机 App)。

3. 代码结构 (Code Structure)

实现该模式的三个关键支柱:

  1. 面向抽象编程:
    • DataCenter 内部持有的不是 ConsoleScreen*,而是 vector<IObserver*>
    • 这使得 DataCenter 对具体的观察者一无所知,只知道它们实现了 Update 接口。
  2. 动态组合 (Attach/Detach):
    • 这是观察者模式区别于模板方法模式的地方。模板方法是静态的继承关系,而观察者模式是动态的聚合关系
    • 你可以在程序运行时(Runtime)随意插拔观察者。
  3. 触发机制 (SetMsg -> Notify):
    • 在状态改变的函数(如 SetMsg)末尾,必须显式调用 Notify() 来驱动整个链条。

4. 符合哪些设计原则?

  • A. 依赖倒置原则 (DIP) —— 灵魂所在
    • 高层模块 (DataCenter) 不依赖 低层模块 (Logger, Screen)。
    • 两者都依赖于 抽象 (IObserver)。
    • 如果 DataCenter 直接 include 了 Logger.h,那就完了,耦合度爆炸。现在它只依赖 IObserver.h
  • B. 开闭原则 (OCP)
    • 对扩展开放: 如果你要加一个“微信通知”,只需要写一个 class Wechat : public IObserver,然后在 mainAttach 进去。
    • 对修改关闭: 你完全不需要修改 DataCenter 类的任何一行代码(不需要改动 Notify 循环)。
  • C. 合成复用原则 (CRP)
    • 多用组合,少用继承。
    • DataCenterIObserver 之间是组合 (Has-A) 关系。这比继承更加灵活,解耦更加彻底。
  • D. 单一职责原则 (SRP)
    • DataCenter:只负责维护数据和分发通知,不负责具体的显示逻辑。
    • Observer:只负责接收数据并处理展示,不负责维护数据源。

5. 如何扩展?

这是检验你是否掌握模式的标准:

  1. 实现抽象接口:
    • class NewDevice : public IObserver
  2. 实现 Update 方法:
    • override Update(const string& msg) { ... }
  3. 动态注册:
    • 在客户端代码中调用 dc.Attach(newDevicePtr);

注意: 整个扩展过程中,Subject (DataCenter) 的源代码是“只读”的,不需要做任何修改。这就是设计模式带来的架构红利。

2.拓展

如何利用开闭原则

  1. 应对稳定点 -> 抽象: 把不通过变的东西抽离出来,写成接口或基类。
  2. 应对变化点 -> 扩展(继承和组合): 把会变的东西通过继承子类或组合对象的方式注入进去。

结合代码中的 class DataCenter(数据中心),这很明显是要实现一个观察者模式(Observer Pattern)。数据中心是核心数据源(稳定),而谁来观察数据、怎么展示数据是多变的(变化)。

1. "应对稳定点,抽象"

  • 对应的代码: IObserver 接口 和 DataCenter::Notify() 方法。
  • 解释: 无论你的业务怎么变,“当数据改变时需要通知一群人”这个机制是稳定的。所以我们将“通知”这个行为抽象成 Update() 接口,将“广播”这个行为写死在 Notify() 里。

2. "应对变化点,扩展(继承和组合)"

这行注释提到了两个核心手段:

  • 继承 (Inheritance):
    • 体现: MonitorScreenLogger 继承 IObserver
    • 作用: 用来定义新的行为。如果你想要一个新的报警方式(比如发邮件),通过继承扩展出一个 EmailAlert 类即可。
  • 组合 (Composition):
    • 体现: DataCenter 中持有 vector<IObserver*> observerList
    • 作用: 用来动态组装对象。DataCenter 不知道具体是谁在观察它,它只是“持有”一堆指针。这使得我们可以在程序运行时(Runtime)随意地 Attach(插拔)观察者,而不需要重新编译 DataCenter 的代码。
posted @ 2025-12-20 20:45  belief73  阅读(1)  评论(0)    收藏  举报