文章目录
-
-
- [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