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

【读书笔记】《C++ Software Design》第一章《The Art of Software Design》

【读书笔记】《C++ Software Design》第一章《The Art of Software Design》

本章通过五项核心指导原则(Guidelines)详细阐述 C++ 软件设计的艺术,结合具体示例和实战建议,提供落地可行的最佳实践。


Guideline 1: Understand the Importance of Software Design

1.1 Features Are Not Software Design

  • 说明:功能实现(Feature)侧重于使软件满足需求,而设计关注系统的整体质量与可演化性。

  • 具体展开

    • 示例对比:假设两个矩阵乘法模块A和B,A直接嵌套三层循环,B通过分块(blocking)优化、面向接口编写、支持并行策略参数化。两者都能“乘矩阵”,但B在性能扩展、单元测试和替换算法时显著优于A。
    • 影响:A模块尽管功能正确,却因高耦合、不易替换、缺乏抽象导致后续维护成本极高。

1.2 Software Design: The Art of Managing Dependencies and Abstractions

  • 依赖管理

    • 依赖倒置:将高层模块依赖于抽象接口(纯虚类或模板概念),如下示例:

      struct ILogger { virtual void log(const std::string&) = 0; };
      class FileLogger : public ILogger { /* ... */ };
      class Processor {std::unique_ptr<ILogger> logger_; 
      public:Processor(std::unique_ptr<ILogger> l): logger_(std::move(l)){}void process(){ logger_->log("start"); /*...*/ }
      };
      
    • 效果:业务逻辑与具体日志实现解耦,可在运行或编译时替换不同 Logger。

  • 抽象层次

    • 分层架构:界面层、业务层、数据层;每层仅通过接口通信。示例:

      • DAO 模块暴露 IDataAccess 接口
      • Service 模块仅持有 std::shared_ptr<IDataAccess>
      • UI 模块调用 Service,无需知道底层实现。

1.3 The Three Levels of Software Development

  1. Level 1: Feature Delivery(功能交付)

    • 特点:快速响应需求,无严格设计;常见于初版或 PoC。
    • 弊端:代码结构混乱、难以测试、难以扩展。
  2. Level 2: Engineering Excellence(工程卓越)

    • 特点:引入流程(CI/CD、代码审查)、工具(静态分析、单元测试)。
    • 实践:使用 Git Hooks 强制代码风格;配置 CMake 脚本自动运行 Google Test。
  3. Level 3: Design Mastery(设计掌握)

    • 特点:深度应用设计原则与模式,关注长期可演化。
    • 实践示例:在插件框架中引入基于 dlopen 的动态加载,并通过工厂模式和反射技术实现模块自动注册。

1.4 The Focus on Features

  • 问题现象:项目初期以功能为中心,业务变化后却发现模块难以拆分与替换。
  • 案例详解:电商系统早期将支付逻辑直接写在订单处理流程中,后续接入新支付渠道需要修改大量订单代码。
  • 解决方案:将支付逻辑提取到 IPaymentProcessor 接口,并用策略模式封装不同渠道,实现无感切换。

1.5 The Focus on Software Design and Design Principles

  • SOLID 在 C++ 中实战

    • Single Responsibility:每个类仅关注一项职责,以 = delete 禁用不相关构造。
    • Open/Closed:通过 CRTP 或模板特化,在不改动原类的情况下扩展功能。
    • Liskov Substitution:子类覆盖时保证前置条件不加强、后置条件不削弱。
  • KISS 与 YAGNI:仅在需求明确时抽象新模块,避免过早引入复杂框架。

  • DRY:利用模板或宏消除重复,如对相似算法提炼为模板函数。

  • Separation of Concerns:UI、逻辑、存储分层;示例代码详见附录A。


Guideline 2: Design for Change

2.1 Separation of Concerns

  • 定义:将系统功能划分为相对独立的模块,每个模块专注单一职责。

  • 实践:使用命名空间、库分割和 CMake target 实现物理分层。

  • 示例:图像处理管道分为:

    1. 格式解析模块
    2. 像素变换模块
    3. 编码输出模块
      每一模块与其他仅通过纯函数或接口交互。

2.2 An Example of Artificial Coupling

  • 场景:订单服务依赖用户服务、库存服务,但实际业务仅需调用库存接口。
  • 问题:直接包含用户头文件引入不必要依赖,导致编译联动。
  • 重构:引入 IInventory 接口,将与用户相关的聚合逻辑移至外部 Adapter。

2.3 Logical Versus Physical Coupling

  • 逻辑耦合:概念依赖,通过接口解耦后仍需关注功能调用顺序。
  • 物理耦合:编译时依赖,通过前向声明、PImpl 等技术消除。
  • 示例:使用 PImpl 隐藏私有成员,减少头文件依赖。

2.4 Don’t Repeat Yourself

  • 实例:多个模块都有类似的配置加载与验证逻辑。
  • 重构前:各自实现 loadConfig() 方法,代码重复。
  • 重构后:提取 ConfigLoader<T> 模板类,支持所有模块配置加载,消除冗余。

2.5 Avoid Premature Separation of Concerns

  • 警示:过早抽象会导致不必要的复杂接口。
  • 衡量准则:当有至少两个模块出现相同代码时再抽象;使用 grep 或代码度量工具辅助决策。

Guideline 3: Separate Interfaces to Avoid Artificial Coupling

3.1 Segregate Interfaces to Separate Concerns

  • 接口隔离原则(ISP):将大接口拆分为多个小接口,避免客户端依赖不必要的方法。

  • C++ 示例

    struct IReadable { virtual std::string read() = 0; };
    struct IWritable { virtual void write(const std::string&) = 0; };
    struct IReadWrite : IReadable, IWritable {};
    
  • 效果:读取组件仅实现 IReadable,写入组件仅实现 IWritable,减少耦合。

3.2 Minimizing Requirements of Template Arguments

  • 问题:过度依赖模板参数的成员函数会导致难以重用。

  • 最佳实践

    • 使用 C++20 Concepts 明确约束,如 template<Readable R> void func(R& r)
    • 对功能特征进行分层接口,避免单个模板参数承担过多职责。
  • 示例:实现通用 serialize 模板,仅依赖 toString() 方法,而非整个对象。


Guideline 4: Design for Testability

4.1 How to Test a Private Member Function

  • 常见做法

    1. 使用 friend 声明测试类访问私有成员。
    2. 通过宏映射将私有方法编译为公共。
  • 弊端:破坏封装。

  • 示例

    class Foo { private: int compute();friend class FooTest;
    };
    

4.2 The True Solution: Separate Concerns

  • 核心思路:将复杂逻辑从私有方法中抽离到独立类或函数,保证可直接测试。

  • 示例重构

    • 原始 Foo::process() 内部含 5 步算法:

      int a = step1(); int b = step2(a); ...
      
    • 重构后:

      class Processor { public: int step1(); int step2(int); };
      Foo holds Processor; // 在测试中直接 new Processor()
      
  • 好处:无需暴露私有成员,即可对每个步骤单元测试。


Guideline 5: Design for Extension

5.1 The Open-Closed Principle

  • 定义:对扩展开放,对修改关闭。

  • C++ 实现

    • 虚函数基类 + 派生扩展
    • 策略模式:将可变行为封装为策略对象
    • 通过插件系统 .so 动态加载新的实现
  • 示例

    struct IFormatter { virtual std::string format(int) = 0; };
    class JsonFormatter : public IFormatter {/*...*/};
    class XmlFormatter : public IFormatter {/*...*/};
    

5.2 Compile-Time Extensibility

  • 模板元编程:利用模板特化、整型常量、if constexpr 实现编译期分支。

  • 参数化多态:CRTP、Traits 类。

  • 示例

    template<typename T> struct Serializer;
    template<> struct Serializer<Json> { static void serialize(...); };
    

5.3 Avoid Premature Design for Extension

  • 问题:过早设计插件机制或策略接口,若只有一个实现反而增加复杂度。

  • 建议

    • 根据 YAGNI 原则,仅在第二个实现需求出现时才引入扩展点。
    • 使用度量工具跟踪实现数量动态决策。

本章小结

  • 软件设计是一门结合科学严谨、工程流程与艺术创意的综合学科。
  • 五大指导原则(了解设计、应对变化、隔离接口、可测试性、可扩展性)协同作用。
  • 在 C++ 中,灵活运用模板、虚函数、多态、RAII 等特性,是实现高质量设计的关键。
  • 实战中,需根据项目规模与团队成熟度,平衡抽象深度与实现复杂度。
http://www.xdnf.cn/news/1111393.html

相关文章:

  • 【一起来学AI大模型】RAG系统组件:检索器(LangChain)
  • Python 实战:构建可扩展的命令行插件引擎
  • 试用了10款翻译软件后,我只推荐这一款!完全免费还超好用
  • 挖矿病毒判断与处理 - 入门
  • DBeaver连接MySQL8.0报错Public Key Retrieval is not allowed
  • Redis集群会有写操作丢失吗?为什么?
  • 1. 好的设计原则
  • C++法则21:避免将#include放在命名空间内部。
  • 箭头函数(Arrow Functions)和普通函数(Regular Functions)
  • 【JVM|类加载】第三天
  • 《汇编语言:基于X86处理器》第7章 整数运算(3)
  • AI:机器人未来的形态是什么?
  • 商业智能(BI)系统深度解析
  • 希尔排序和选择排序及计数排序的简单介绍
  • 【学习笔记】Nginx常用安全配置
  • QWidget的属性
  • 华为业务变革项目IPD基本知识
  • 前端面试宝典---项目难点2-智能问答对话框采用虚拟列表动态渲染可视区域元素(10万+条数据)
  • 一文理解缓存的本质:分层架构、原理对比与实战精粹
  • TinyBERT:知识蒸馏驱动的BERT压缩革命 | 模型小7倍、推理快9倍的轻量化引擎
  • 多模态大模型》多模态基础模型》多模态对齐、融合和表示
  • 27. 移除元素
  • 浅谈 Python 中的 yield——yield的返回值与send()的关系
  • 关于数字签名
  • 容器化改造避坑指南:传统应用迁移K8s的10个关键节点(2025实战复盘)
  • 【Go + Gin 实现「双 Token」管理员登录】
  • linux系统----LVS负载均衡集群(NET/DR)模式
  • Arduino 无线通信实战:使用 RadioHead实现 315MHz 433M模块数据传输
  • net.createServer详解
  • 【Flask】基础入门