当前位置: 首页 > 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.lryc.cn/news/586265.html

相关文章:

  • 【大模型面试】50道大型语言模型(LLM)面试问题汇总,看完少走99%弯路!
  • 不止于监控:深入剖析OpenTelemetry的可观察性生态体系
  • LeetCode 3169.无需开会的工作日:排序+一次遍历——不需要正难则反,因为正着根本不难
  • 暑期前端训练day6
  • 历史数据分析——云南白药
  • 连接池的核心接口和常用属性
  • 基于大模型的鼻咽癌全周期预测及诊疗优化研究报告
  • SQL新手入门详细教程和应用实例
  • 零基础 “入坑” Java--- 九、类和对象(二)
  • 芯片验证之验证策略
  • 【MogDB】一种基于ctid分片并发查询以提升大表查询性能的方式
  • 68 指针的减法操作
  • 【Datawhale AI夏令营】Task2 笔记:MCP Server开发的重难点
  • 使用包管理工具CocoaPods、SPM、Carthage的利弊与趋势
  • tiktok 弹幕 逆向分析
  • 系统性能评估方法深度解析:从经典到现代
  • 数据湖和数据库对比
  • 多层感知机的简洁实现
  • Spring Cloud Gateway中常见的过滤器
  • 【时间之外】尘封的智能套件复活记
  • 【QGC】深入解析 QGC 配置管理
  • Gas and Gas Price
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十课——图像gamma矫正的FPGA实现
  • Git企业级开发(最终篇)
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十一课——图像均值滤波的FPGA实现
  • TCP的socket编程
  • OneCode 3.0架构深度剖析:工程化模块管理与自治UI系统的设计与实现
  • 多路选择器的学习
  • 前端面试专栏-算法篇:24. 算法时间与空间复杂度分析
  • TCP与UDP协议详解:网络世界的可靠信使与高速快递