当前位置: 首页 > news >正文

【读书笔记】《C++ Software Design》第十章与第十一章 The Singleton Pattern The Last Guideline

《C++ Software Design》第十章与第十一章 The Singleton Pattern & The Last Guideline

在很多开发者心中,Singleton(单例模式) 是设计模式的经典代表,广泛用于日志系统、配置中心、资源管理等场景。然而,《C++ Software Design》的第十章提醒我们:Singleton 其实更像是实现技巧而非架构思想。本章围绕单例的设计问题展开批判性分析,并提供更现代、可测试、易维护的替代方案。

第十一章作为本书收尾,对学习设计模式提出重要建议:设计模式不是目标,而是通向架构思维的工具


第十章:重新审视 Singleton 模式的使用与替代

Guideline 37:将 Singleton 视为实现细节,而非设计理念

什么是 Singleton?

Singleton 的目标是:确保类在系统中只有一个实例,并提供全局访问点

经典实现如下:

class Logger {
public:static Logger& instance() {static Logger inst;return inst;}void log(const std::string& msg) {std::cout << "[LOG] " << msg << "\n";}private:Logger() = default;
};

调用方式:

Logger::instance().log("System started.");

听起来没问题?其实隐藏了重大的设计隐患


Singleton 的本质问题

“Singleton 模式不是用来解决依赖问题的,而是绕开依赖注入的。”

主要缺陷:
  1. 隐式依赖:你无法从函数签名看出它是否依赖单例;
  2. 不可测试:单例通常无法替换为 mock 或 fake;
  3. 状态污染:多个测试用例运行时共享状态,容易出现隐式耦合;
  4. 生命周期不受控:程序难以在特定阶段“重置”或清除全局状态;
  5. 难以组合:与其它策略模式、配置体系耦合度高。

Guideline 38:设计可替代、可测试的 Singleton

认识本质:Singleton 是全局状态

如果你把单例对象当作状态容器,那么你就会意识到它带来的是“全局共享变量的封装体”。

class ConfigManager {
public:std::string get(const std::string& key);
};

本质上你在访问一个静态变量,只不过套了个类。程序越大,越容易失控。


Singleton 如何阻碍可变性与可测试性?

例子:

class Authenticator {
public:bool login(const std::string& user, const std::string& pass) {Logger::instance().log("User login attempt.");// ...}
};

问题在于:

  • 测试 Authenticator 时不能 mock 掉 Logger
  • 甚至不能确保 log 被正确写入
  • 需要手动重置状态、捕获输出,非常脆弱

更好的做法:反转依赖 + 局部注入

Logger 从单例转换为可替换依赖:

class ILogger {
public:virtual void log(const std::string&) = 0;virtual ~ILogger() = default;
};class ConsoleLogger : public ILogger {
public:void log(const std::string& msg) override {std::cout << msg << "\n";}
};class Authenticator {ILogger& logger;
public:Authenticator(ILogger& l) : logger(l) {}bool login(const std::string& user, const std::string& pass) {logger.log("Login attempt");return true;}
};
  • 使用接口(Strategy 模式)
  • 支持注入任意实现(如 MockLogger)
  • 不再依赖全局状态

局部注入的迁移策略

  1. 识别所有对单例的使用点
  2. 将单例改为接口(如 ILogger
  3. 替换调用点为构造注入、函数参数注入
  4. 最后完全移除 Singleton 实现,只保留实例化层使用

这就是“从 Singleton 走向依赖注入”的过程。


第十一章:继续学习设计模式的建议

Guideline 39:持续学习设计模式,别止步于列表记忆

不要将设计模式当作 API 说明书

很多开发者学习设计模式是为了“面试背题”或“记住几个经典结构”。但真实的软件工程场景中:

  • 设计模式应当被灵活组合
  • 模式之间存在转换关系与融合
  • 每种模式都依赖于语言、语义、场景的适配

模式 ≠ 静态结构,模式 = 可演化的语言

设计模式是人们用来交流复杂架构思想的“语言工具”:

模式本质功能替代表达
Strategy行为切换函数对象 / 多态
Singleton全局共享 + 惰性初始化注入 / 缓存 / SFO
Decorator可组合行为增强模板 / 类型擦除
CRTP编译期策略注入Concepts(C++20)
Prototype运行时复制能力clone / value-copy
Adapter接口变换Wrapper / Conversion

你应当思考这些模式如何与 C++ 模板系统、类型系统、运行时模型协作,而非照本宣科地“照搬结构图”。


学习建议

  • 阅读现代库源码(如 Boost, Abseil, Folly)掌握实际模式演化
  • 关注语言发展,如 C++20 Concepts 如何替代 CRTP
  • 使用组合优先替代继承,追求“行为解耦”而非“类关系构造”
  • 编写可测试、可演化的架构代码,让设计模式成为服务于变更的工具

总结

内容核心关键要点
Singleton 是实现细节,不是架构主张它隐藏依赖、妨碍测试、耦合状态
更好的方式是依赖注入和接口分离用 Strategy 模式组合替代
模式之间应灵活组合、替换、转化不应生搬硬套传统结构图
学习设计模式是设计思维的启蒙最终目标是写出可演化的系统

架构思维

不要被模式束缚,而是要用模式服务于可维护性与系统演化

从设计单例到重构为依赖注入,从背诵模式结构图到理解它们的演化语义,我们才能真正迈入“架构设计”的世界。

设计模式不是答案,而是探索的起点。

http://www.dtcms.com/a/277826.html

相关文章:

  • MyBatis04-MyBatis小技巧
  • 【读书笔记】《Effective Modern C++》第六章 Lambda Expressions
  • Spring AI多模态API初体验:文字、图片、语音,一个接口全搞定!
  • 【研报复现】开源证券:均线的收敛与发散
  • DevOps
  • 深度学习图像分类数据集—玉米粒质量识别分类
  • 设计模式之单例模式:深入解析全局唯一对象的艺术
  • JVM 锁自动升级机制详解
  • 哈希扩展 --- 布隆过滤器
  • 肿瘤浸润淋巴细胞是什么,与三级淋巴结构的关系
  • 会计 - 22 - 外币折算
  • Linux713 SAMBA;磁盘管理:手动挂载,开机自动挂载,自动挂载
  • 补:《每日AI-人工智能-编程日报》--2025年7月12日
  • CTFSHOW pwn161 WP
  • 如何成为 PostgreSQL 中级专家
  • 论文学习_SemDiff: Binary Similarity Detection by Diffing Key-Semantics Graphs
  • 4G PPP模式与以太网接口在LwIP中的融合应用
  • JAVA AI智能体——1 入门
  • Redis 基础详细介绍(Redis简单介绍,命令行客户端,Redis 命令,Java客户端)
  • day5--上传视频
  • AI赋能ERP:从自动化到智能化,企业运营的未来已来
  • 【SpringBoot】注册条件+自动配置原理+自定义starter
  • 每天学习一个Python第三方库之jieba库
  • 【DVWA系列】——File Upload——low详细教程(webshell工具冰蝎)
  • on-policy和offpolicy算法
  • 计算机时钟演进:从毫秒到纳秒的精密革命
  • 动态规划题解_零钱兑换【LeetCode】
  • AV1序列头信息
  • Leetcode 3615. Longest Palindromic Path in Graph
  • [Dify]-基础入门5- Dify 中角色设定的正确方式与常见误区