【C++代码整洁之道】第九章 设计模式和习惯用法

文章目录

      • [1. 设计原则与设计模式](#1. 设计原则与设计模式)
      • [2. 常见的设计模式及应用场景](#2. 常见的设计模式及应用场景)
        • [2.1 单例模式](#2.1 单例模式)
        • [2.2 依赖注入](#2.2 依赖注入)
        • [2.3 Adapter模式](#2.3 Adapter模式)
        • [2.4 Strategy模式](#2.4 Strategy模式)
        • [2.5 Command模式](#2.5 Command模式)
        • [2.6 Command处理器模式](#2.6 Command处理器模式)
        • [2.7 Composite模式](#2.7 Composite模式)
        • [2.8 Observer模式](#2.8 Observer模式)
        • [2.9 Factory模式](#2.9 Factory模式)
        • [2.10 Facade模式](#2.10 Facade模式)
        • [2.11 Money Class模式](#2.11 Money Class模式)
        • [2.12 特例模式](#2.12 特例模式)
      • [3. 常见的设计模式及应用场景](#3. 常见的设计模式及应用场景)

《C++代码整洁之道》学习笔记,系列之九:设计模式和习惯用法


不要重新造轮子!

《设计模式 可复用面向对象软件的基础》书中提出了23种面向对象的设计模式,在软件领域,直到现在这本书仍然被认为是最重要的一本书

1. 设计原则与设计模式

  • (1)设计原则作为决策基础的基本真理,在大多数情况下,原则是独立于某种编程范式或技术的
  • (2)原则更持久、更重要,如果已经深入理解了原则,就可以找到一个合适的设计模式来解决特定的问题
  • (3)不要夸大设计模式的作用,过度使用会遇到过度设计的痛苦

2. 常见的设计模式及应用场景

2.1 单例模式
  • 单例模式经常用于所谓的日志记录、数据库链接、中央用户管理或表示来自现实世界的东西。但是,单例模式几乎总是带有一种设计的臭味

  • 确保一个类只有唯一的实例,并提供对该实例的全局访问

    • a.一方面,这个模式的任务是控制和管理其整个生命周期的唯一实例
    • b.另一方面,提供了对实例的全局访问,程序中所有其他对象都可使用该实例
  • 在C++11之后,Meyers单例getInstance()默认是线程安全 的,但不意味着单例类中其他成员函数都是线程安全的

  • 在软件设计中,对单例对象的所有依赖都隐藏在了代码中,面向对象中的单例就像面向过程中全局变量的使用一样

  • 所有使用单例的匿名客户端类都与它紧密耦合在一起,这种情况没法保证单元测试的独立性;而且由于新的或需求变化而不得不更改单例,可能会触发所有依赖类的一系列更改

  • 解决的方法是:只创建一个实例,并在需要的地方注入它

2.2 依赖注入
  • 依赖注入DI = Dependency Injection

  • 构造函数注入 :为了摆脱使用单例模式,使用依赖倒置原则DIP进行依赖注入,在实现中删除单例依赖,使用智能指针作为另一个类的初始化列表来创建构造函数,以使用智能指针指向的对象。这样能够促进松耦合,并且能够提高可测试性

  • setter注入:也可以提供setter方法,客户端对象为服务端对象提供setter,例如使用普通函数使用智能指针参数,使用指针指向的logger对象

  • 依赖注入是一种设计模式,可以提高松耦合、具有很好的可配置性、提高软件系统的可测试性等

2.3 Adapter模式
  • Adapter模式:把一个类的接口转换为客户端期望的另一个接口。比如需要使用BoostLog进行日志记录,可以提供一个Logger接口的另一个实现(logger接口本身作为依赖注入),将BoostLog的接口适配到我们要使用的接口

  • Adapter可以为不兼容的接口提供广泛的适配和转换的可能性,但是如果接口差异过大,adapter代码的实现可能会非常复杂

2.4 Strategy模式
  • 策略模式 :以不同的方式做同一件事情,比如借助策略模式使用时根据排序列表的不同属性,动态选择不同的排序算法

策略模式是开放封闭OC原则的体现

  • 比如一个抽象类 Formatter,三个特定格式化程序(PlainText, XML, JSON)重写Formatter的format纯虚函数
2.5 Command模式
  • command模式背后的指导原则是松耦合原则和关注点分离原则

  • command模式的一个很好的例子是Client /Server架构体系,Client发送命令给Server,Server接收并执行命令,Server只知道Command接口(比如睡眠接口、输出接口等),不知道任何具体的命令类

  • 最好补充UML类图 - to do

2.6 Command处理器模式
  • 根据接口隔离原则ISP
2.7 Composite模式
  • composite模式:将对象组合成树结构来表示"部分-整体"的层次结构,这个组合允许客户端统一的处理单个对象和对象的组合

  • 在composite模式的帮助下,可以把简单的command(叶子)组装成复杂的command序列

2.8 Observer模式
  • observer模式:定义对象之间一对多的依赖关系,以便在一个对象更改状态时,自动通知并更新其所有的依赖关系

  • 该模式的核心NotifyAllObservers()成员函数(这个功能的函数)

  • 例子:三个视图,饼图、柱状图、表格,变更数据之后所有视图都update更新结果

2.9 Factory模式
  • 与依赖注入不同,如果需要运行时创建对象,可以使用工厂模式
2.10 Facade模式
  • facade模式:为子系统定义一个更高级的接口(定义明确且简单的接口),使得子系统更容易使用,任何子系统的访问都必须通过facade完成
2.11 Money Class模式
  • 在计算中不能写这样的语句if sum == 3.0,因为浮点数的精度是无法保证的 。除了可以用整型来存储数值,文中还提到一种Money Class模式,提供了一个Money类来表示确切的金额,在c++中Money类有不同的实现方式
2.12 特例模式
  • 文中建议不要传递0(NULL, nullptr),当然传递列表并判空好一些,某些情况下可能还不够好,需要返回一个可以查询内部问题的返回值

  • 比如设置一个customer类的子类notfoundcustomer,这个类对象可以完成很多事情,进行很多判断,而且它总有一个有效的对象。当然在not found为非预期异常的情况下是需要捕获异常的

3. 常见的设计模式及应用场景

  • 编程的习惯用法:只适用于限定的编程语言或特定的技术,如框架

  • 比如c++的#pragma once用于预处理阶段只包含一次头文件,还有RAII的习惯用法等

  • 网上能搜到大量的C++习惯用法,但是这些用法可能不适合现代的整洁的代码、也可能已经过时

  • 文中举了一些例子,如不可变类、模板类匹配、copy-and-swap用法(拷贝赋值运算符调用noexcept的swap实现)、pimpl习惯用法(通过类中定义impl类隐藏实现细节,避免所有该文件的所有文件都重新编译,提高编译时间)等


【参考文章】

1\]. C++代码整洁之道 create by shuaixio, 2025.04.04

相关推荐
@hdd33 分钟前
C++ | 文件读写(ofstream/ifstream/fstream)
c++·文件
敢敢のwings36 分钟前
C++信号与槽机制自实现
开发语言·数据库·c++
·醉挽清风·39 分钟前
学习笔记—C++—入门基础()
c语言·开发语言·c++·笔记·学习·算法
照书抄代码41 分钟前
C++11观察者模式示例
开发语言·c++·观察者模式
wjm04100642 分钟前
C++的四种类型转换
java·开发语言·c++
今夜有雨.1 小时前
使用C++实现HTTP服务
开发语言·网络·c++·后端·网络协议·tcp/ip·http
百渡ovO1 小时前
【蓝桥杯】每日练习 Day21
c++·算法·蓝桥杯
我命由我123451 小时前
C++ - 头文件基础(常用标准库头文件、自定义头文件、头文件引入方式、防止头文件重复包含机制)
服务器·c语言·开发语言·c++·后端·visualstudio·visual studio code
别致的影分身1 小时前
Protobuf 的快速使用(四)
服务器·网络·c++
李匠20242 小时前
C++学习之LINUX网络编程-套接字通信基础
c++·学习