设计模式(C++)详解——观察者模式(Observer)(2)
🌟 观察者模式大冒险:一场代码世界的订阅狂欢!🌟
想象一下,你是一个网红博主,每次发布新视频,你的百万粉丝都会立即收到通知!这就是观察者模式的魔力!
🎭 第一章:缘起 - 一个网红博主的烦恼
从前,有个叫"代码侠"的博主,他每次发布新视频都要手动通知每个粉丝:
// 糟糕的旧时代
void 发布视频() {制作视频();打电话通知(粉丝1); // 😫 累死了!打电话通知(粉丝2);发邮件通知(粉丝3);// ... 还有99997个粉丝要通知
}
"代码侠"累得筋疲力尽,直到他遇到了观察者模式这个魔法!
🎯 核心概念:订阅的魔力
// 新时代的智慧
void 发布视频() {制作视频();一键通知所有粉丝(); // 🎉 太轻松了!
}
观察者模式的四大天王:
角色 | 现实比喻 | 职责 |
---|---|---|
Subject(主题) | 网红博主 | 维护粉丝列表,发布内容 |
Observer(观察者) | 粉丝 | 接收更新通知 |
ConcreteSubject | "代码侠"频道 | 具体的发布者 |
ConcreteObserver | 铁杆粉丝 | 具体的订阅者 |
🏰 第二章:魔法城堡 - 观察者模式的C++实现
🧙♂️ 魔法咒语:基础接口
/*** 🎭 粉丝接口(观察者)* * 每个粉丝都必须实现"收到通知"这个方法* 当博主发布新内容时,所有粉丝都会自动收到通知!*/
class 粉丝 {
public:virtual ~粉丝() = default;/*** @brief 收到新内容通知* * 就像手机弹出推送通知一样!* * @param 博主 谁发布的内容* @param 内容 发布的具体内容*/virtual void 收到通知(const std::string& 博主, const std::string& 内容) = 0;/*** @brief 粉丝名字*/virtual std::string 名字() const = 0;
};/*** 🎬 博主接口(主题)* * 博主需要管理粉丝列表,支持粉丝关注和取关*/
class 博主 {
public:virtual ~博主() = default;/*** @brief 新增粉丝(关注)* * @param 新粉丝 想要关注的新粉丝*/virtual void 新增粉丝(std::shared_ptr<粉丝> 新粉丝) = 0;/*** @brief 移除粉丝(取关)* * @param 旧粉丝 想要取关的粉丝*/virtual void 移除粉丝(std::shared_ptr<粉丝> 旧粉丝) = 0;/*** @brief 发布新内容* * 像魔法一样,自动通知所有粉丝!* * @param 内容 要发布的内容*/virtual void 发布内容(const std::string& 内容) = 0;
};
🏰 具体实现:网红博主的日常
#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <string>/*** 🎬 具体博主:代码侠频道* * 我们的网红博主,拥有百万粉丝!* 使用weak_ptr管理粉丝,避免循环引用导致的内存泄漏*/
class 代码侠频道 : public 博主 {
private:std::string 频道名字_;std::vector<std::weak_ptr<粉丝>> 粉丝列表_; // 🎯 关键技巧!public:/*** @brief 构造函数* * @param 名字 频道名称*/代码侠频道(const std::string& 名字) : 频道名字_(名字) {std::cout << "🎉 新频道诞生:《" << 名字 << "》上线啦!" << std::endl;}void 新增粉丝(std::shared_ptr<粉丝> 新粉丝) override {// 检查是否已经关注auto 是否存在 = std::find_if(粉丝列表_.begin(), 粉丝列表_.end(),[&新粉丝](const std::weak_ptr<粉丝>& 弱粉丝) {return !弱粉丝.expired() && 弱粉丝.lock() == 新粉丝;});if (是否存在 == 粉丝列表_.end()) {粉丝列表_.emplace_back(新粉丝);std::cout << "👏 《" << 新粉丝->名字() << "》关注了《" << 频道名字_ << "》!粉丝数:" << 粉丝数量() << std::endl;}}void 移除粉丝(std::shared_ptr<粉丝> 旧粉丝) override {auto 原来大小 = 粉丝列表_.size();粉丝列表_.erase(std::remove_if(粉丝列表_.begin(), 粉丝列表_.end(),[&旧粉丝](const std::weak_ptr<粉丝>& 弱粉丝) {return 弱粉丝.expired() || 弱粉丝.lock() == 旧粉丝;}),粉丝列表_.end());if (粉丝列表_.size() < 原来大小) {std::cout << "😢 《" << 旧粉丝->名字() << "》取关了《" << 频道名字_ << "》!粉丝数:" << 粉丝数量() << std::endl;}}void 发布内容(const std::string& 内容) override {std::cout << "\n🚀【" << 频道名字_ << "】发布新内容:" << 内容 << std::endl;// 🧹 先清理失效的粉丝(比如注销账号的)清理失效粉丝();// 📢 然后通知所有有效粉丝int 通知数量 = 0;for (auto& 弱粉丝 : 粉丝列表_) {if (auto 粉丝指针 = 弱粉丝.lock()) {粉丝指针->收到通知(频道名字_, 内容);通知数量++;}}std::cout << "✅ 成功通知 " << 通知数量 << " 位粉丝!" << std::endl;}/*** @brief 获取当前粉丝数量*/size_t 粉丝数量() const {return std::count_if(粉丝列表_.begin(), 粉丝列表_.end(),[](const std::weak_ptr<粉丝>& 弱粉丝) {return !弱粉丝.expired();});}private:/*** @brief 清理失效粉丝* * 就像定期清理不活跃用户一样*/void 清理失效粉丝() {auto 原来数量 = 粉丝列表_.size();粉丝列表_.erase(std::remove_if(粉丝列表_.begin(), 粉丝列表_.end(),[](const std::weak_ptr<粉丝>& 弱粉丝) {return 弱粉丝.expired();}),粉丝列表_.end());if (粉丝列表_.size() < 原来数量) {std::cout << "🧹 清理了 " << (原来数量 - 粉丝列表_.size()) << " 个失效粉丝" << std::endl;}}
};/*** 🎯 具体粉丝类型:普通粉丝* * 普通的视频观众,收到通知会很开心!*/
class 普通粉丝 : public 粉丝, public std::enable_shared_from_this<普通粉丝> {
private:std::string 粉丝名字_;int 开心程度_; // 🎭 粉丝的情绪状态public:普通粉丝(const std::string& 名字) : 粉丝名字_(名字), 开心程度_(50) {}void 收到通知(const std::string& 博主, const std::string& 内容) override {开心程度_ += 10; // 收到通知更开心了!std::cout << " 💌 " << 粉丝名字_ << " 收到《" << 博主 << "》的推送:" << 内容 << std::endl;std::cout << " 😊 开心程度:" << 开心程度_ << "/100" << std::endl;// 随机决定是否点赞评论if (开心程度_ > 70) {std::cout << " 👍 忍不住点了个赞!" << std::endl;}}std::string 名字() const override {return 粉丝名字_;}/*** @brief 获取当前开心程度*/int 获取开心程度() const { return 开心程度_; }
};/*** 🦸 具体粉丝类型:超级粉丝* * 铁杆粉丝,每次都会点赞、评论、转发三连!*/
class 超级粉丝 : public 粉丝, public std::enable_shared_from_this<超级粉丝> {
private:std::string 粉丝名字_;int 三连次数_;public:超级粉丝(const std::string& 名字) : 粉丝名字_(名字), 三连次数_(0) {}void 收到通知(const std::string& 博主, const std::string& 内容) override {std::cout << " 🎯 " << 粉丝名字_ << " 收到偶像《" << 博主 << "》的推送!" << std::endl;// 自动三连!std::cout << " 🔥 立即点赞、评论、转发三连!" << std::endl;三连次数_++;if (三连次数_ % 5 == 0) {std::cout << " 🏆 达成 " << 三连次数_ << " 次三连成就!" << std::endl;}}std::string 名字() const override {return 粉丝名字_ + "(超级粉丝)";}
};
🎪 魔法演示:博主的精彩日常
/*** 🎪 主函数:网红博主的精彩日常* * 让我们看看观察者模式如何让内容发布变得如此轻松!*/
int main() {std::cout << "🌈 ===================================" << std::endl;std::cout << "🌟 观察者模式大冒险:网红博主篇 🌟" << std::endl;std::cout << "🌈 ===================================" << std::endl;// 🎬 创建我们的网红博主auto 代码侠 = std::make_shared<代码侠频道>("代码侠的编程乐园");std::cout << "\n🎯 第一阶段:粉丝集结!" << std::endl;// 👥 创建各种粉丝auto 小明 = std::make_shared<普通粉丝>("小明");auto 小红 = std::make_shared<普通粉丝>("小红"); auto 铁杆张 = std::make_shared<超级粉丝>("张大哥");auto 技术李 = std::make_shared<超级粉丝>("李大师");// ➕ 粉丝关注博主代码侠->新增粉丝(小明);代码侠->新增粉丝(小红);代码侠->新增粉丝(铁杆张);代码侠->新增粉丝(技术李);std::cout << "\n📊 当前粉丝统计:" << 代码侠->粉丝数量() << " 人" << std::endl;std::cout << "\n🎬 第二阶段:内容发布狂欢!" << std::endl;// 🚀 博主发布内容,自动通知所有粉丝!代码侠->发布内容("C++设计模式入门教程");代码侠->发布内容("观察者模式的实战应用");代码侠->发布内容("智能指针的深度解析");std::cout << "\n🔄 第三阶段:粉丝动态变化" << std::endl;// 小红取关了 😢代码侠->移除粉丝(小红);// 新粉丝加入 🎉auto 新粉丝 = std::make_shared<普通粉丝>("新手小王");代码侠->新增粉丝(新粉丝);std::cout << "\n📊 更新后粉丝统计:" << 代码侠->粉丝数量() << " 人" << std::endl;// 继续发布内容代码侠->发布内容("现代C++新特性介绍");std::cout << "\n🧪 第四阶段:智能指针魔法测试" << std::endl;{// 临时粉丝,测试生命周期管理auto 临时粉丝 = std::make_shared<普通粉丝>("临时观众");代码侠->新增粉丝(临时粉丝);std::cout << "📈 临时粉丝加入后:" << 代码侠->粉丝数量() << " 人" << std::endl;// 临时粉丝离开作用域,自动销毁}// 🎯 这里不会崩溃!weak_ptr自动处理了失效粉丝代码侠->发布内容("高级主题:元编程入门");std::cout << "📉 临时粉丝离开后:" << 代码侠->粉丝数量() << " 人" << std::endl;std::cout << "\n🎊 ===================================" << std::endl;std::cout << "🎉 演示圆满结束! 🎉" << std::endl;std::cout << "🎊 ===================================" << std::endl;return 0;
}
运行结果预览:
🌈 ===================================
🌟 观察者模式大冒险:网红博主篇 🌟
🌈 ===================================🎯 第一阶段:粉丝集结!
🎉 新频道诞生:《代码侠的编程乐园》上线啦!
👏 《小明》关注了《代码侠的编程乐园》!粉丝数:1
👏 《小红》关注了《代码侠的编程乐园》!粉丝数:2
👏 《张大哥(超级粉丝)》关注了《代码侠的编程乐园》!粉丝数:3
👏 《李大师(超级粉丝)》关注了《代码侠的编程乐园》!粉丝数:4🎬 第二阶段:内容发布狂欢!🚀【代码侠的编程乐园】发布新内容:C++设计模式入门教程💌 小明 收到《代码侠的编程乐园》的推送:C++设计模式入门教程😊 开心程度:60/100💌 小红 收到《代码侠的编程乐园》的推送:C++设计模式入门教程 😊 开心程度:60/100🎯 张大哥(超级粉丝) 收到偶像《代码侠的编程乐园》的推送!🔥 立即点赞、评论、转发三连!🎯 李大师(超级粉丝) 收到偶像《代码侠的编程乐园》的推送!🔥 立即点赞、评论、转发三连!
✅ 成功通知 4 位粉丝!
🗺️ 第三章:冒险地图 - 观察者模式的多种应用
🏦 场景一:股票市场监控系统
想象你是个股民,同时监控多支股票:
/*** 📈 股民观察者* * 就像手机上的股票APP,股价变动立即推送!*/
class 股民 : public 粉丝 {
private:std::string 名字_;double 买入阈值_;double 卖出阈值_;public:股民(const std::string& 名字, double 买入价, double 卖出价): 名字_(名字), 买入阈值_(买入价), 卖出阈值_(卖出价) {}void 收到通知(const std::string& 股票, const std::string& 价格文本) override {double 价格 = std::stod(价格文本);std::cout << "📱 " << 名字_ << " 收到 " << 股票 << " 价格警报:" << 价格 << "元" << std::endl;if (价格 <= 买入阈值_) {std::cout << " 💰 机会!低于买入价,果断加仓!" << std::endl;} else if (价格 >= 卖出阈值_) {std::cout << " 💸 警惕!高于卖出价,考虑减仓!" << std::endl;} else {std::cout << " ⏳ 价格在区间内,继续观望..." << std::endl;}}std::string 名字() const override { return 名字_ + "(股民)"; }
};
🎮 场景二:游戏成就系统
想象你在玩游戏,自动解锁各种成就:
/*** 🏆 成就系统观察者* * 像游戏里的成就系统,自动检测玩家行为并解锁成就!*/
class 成就系统 : public 粉丝 {
private:std::vector<std::string> 已解锁成就_;public:void 收到通知(const std::string& 事件类型, const std::string& 事件内容) override {std::cout << "🎮 成就系统检测到事件:" << 事件类型 << " - " << 事件内容 << std::endl;if (事件类型 == "首次登录" && !成就已解锁("欢迎来到游戏")) {解锁成就("欢迎来到游戏");} else if (事件类型 == "击败Boss" && !成就已解锁("Boss杀手")) {解锁成就("Boss杀手");} else if (事件类型 == "收集完成" && !成就已解锁("收藏家")) {解锁成就("收藏家");}}std::string 名字() const override { return "成就系统"; }private:void 解锁成就(const std::string& 成就名) {已解锁成就_.push_back(成就名);std::cout << " 🏆 成就解锁:《" << 成就名 << "》!" << std::endl;}bool 成就已解锁(const std::string& 成就名) {return std::find(已解锁成就_.begin(), 已解锁成就_.end(), 成就名) != 已解锁成就_.end();}
};
🛠️ 第四章:魔法工具 - 完整的项目配置
📦 Makefile:构建我们的魔法世界
# 🎯 编译器配置
CXX := g++
CXX_STD := c++17# 🚀 编译选项
CXXFLAGS := -std=$(CXX_STD) -Wall -Wextra -Wpedantic -O2
DEBUG_FLAGS := -g -DDEBUG -O0
RELEASE_FLAGS := -DNDEBUG -O3# 🎪 目标文件
TARGETS := 网红博主演示 股票监控演示 游戏成就演示# 🏗️ 默认构建所有目标
all: $(TARGETS)# 🎬 网红博主演示
网红博主演示: 观察者模式_博主示例.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 📈 股票监控演示
股票监控演示: 观察者模式_股票示例.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 🎮 游戏成就演示
游戏成就演示: 观察者模式_游戏示例.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 🐛 调试版本
debug: CXXFLAGS += $(DEBUG_FLAGS)
debug: $(TARGETS)# 🧹 清理构建文件
clean:rm -f $(TARGETS) *.o# 📥 安装依赖
install-deps:sudo apt-get updatesudo apt-get install -y g++ build-essential# 🧪 运行所有测试
test: all@echo "🎬 运行网红博主演示..."@./网红博主演示@echo@echo "📈 运行股票监控演示..."@./股票监控演示 @echo@echo "🎮 运行游戏成就演示..."@./游戏成就演示.PHONY: all clean debug install-deps test
🎯 操作指南:启动我们的冒险!
编译命令:
# 安装魔法工具
make install-deps# 构建所有演示
make# 或者直接运行测试
make test
运行示例:
# 运行网红博主演示
./网红博主演示# 运行结果类似:
# 🌈 ===================================
# 🌟 观察者模式大冒险:网红博主篇 🌟
# 🌈 ===================================
# 🎯 第一阶段:粉丝集结!
# 🎉 新频道诞生:《代码侠的编程乐园》上线啦!
# 👏 《小明》关注了《代码侠的编程乐园》!粉丝数:1
# ... (精彩继续)
🎓 第五章:智慧总结 - 观察者模式的精髓
🏆 设计模式的胜利
通过这场冒险,我们学到了:
🎯 观察者模式的核心优势:
- 自动通知:像魔法一样,主题变化自动通知所有观察者
- 松耦合:博主不知道粉丝的具体类型,只知道他们能收通知
- 动态管理:运行时可以随意添加、删除观察者
- 安全可靠:使用
weak_ptr
避免内存泄漏和悬空指针
🛡️ 生命周期管理的智慧:
// ❌ 危险做法:原始指针
std::vector<粉丝*> 粉丝列表_; // 粉丝销毁后会导致崩溃!// ✅ 安全做法:weak_ptr
std::vector<std::weak_ptr<粉丝>> 粉丝列表_; // 自动处理失效粉丝
🚀 实际应用场景:
- 📱 APP推送通知系统
- 📈 金融市场价格监控
- 🎮 游戏事件处理系统
- 🔧 配置热更新系统
- 🌐 微服务消息总线
🎭 故事寓意
观察者模式就像现实世界中的订阅机制:
- 博主发布内容 → 主题状态改变
- 粉丝接收通知 → 观察者更新
- 关注/取关 → 动态注册/注销
这种"发布-订阅"的思维,让我们的代码世界变得更加智能和灵活!
🎊 冒险家,恭喜你! 你已经掌握了观察者模式的精髓。现在就去用这个魔法,构建更加智能、灵活的软件系统吧!记住:好的设计模式,让代码世界变得更加美好!✨