C++20设计模式:可复用的面向对象设计方法:reusable approaches for object-oriented software design,second edition (俄)德米特里·内斯特鲁克(Dmitri Nesteruk)著 , 冯强国译
译者序
你需要有C++基础,了解C++新特性(lambda,auto,override)
第1章 引论1
《设计模式:可复用面向对象软件的基础》ISBN 978-7-111-61833-1就是以c++和smalltalk语言作为例程。
1.1 本书的目标读者2
1.2 关于代码示例2
1.3 关于开发者工具3
1.4 重要概念3
继承
struct MyClass : SomeBase<MyClass>
1.4.1 奇异递归模板模式3
1.4.2 Mixin继承4
1.4.3 旧风格的静态多态5
1.4.4 概念与静态多态6
1.4.5 属性7
1.5 SOLID设计原则8
- 单一职责原则(Single Responsibility Principle, SRP)
- 开闭原则(Open-Closed Principle, OCP)
- 里氏替换原则(Liskov Substitution Principle, LSP)
- 接口隔离原则(Interface Segregation Principle, ISP)
- 依赖倒转原则(Dependency Inversion Principle, DIP)
1.5.1 单一职责原则8
每个类只有一个职责,因此也只有一个修改该类的原因。即只有该职责变化时,该类才做相应的修改。
1.5.2 开闭原则10
我们不必返回到已经编写和测试的代码来修改它。
1.5.3 里氏替换原则15
如果某个接口以基类Parent类型的对象为参数,那么它应该同等地接受子类Child类对象作为参数,并且程序不会产生任何异常。
正方形继承长方形
class Square:public Rectangle
{}// 接口以Parent作为参数
void process(Rectangle& r)
1.5.4 接口隔离原则17
基本思想:将复杂的接口分离为多个单独的接口,以免强制实现者必须实现某些他们实际上并不需要的接口。
反例:多功能打印机可以完成打印接口,扫描接口
struct IMachine
{virtual void print(vector<Document*> docs) = 0;virtual void scan(vector<Document*> docs) = 0;
}
存在的问题:也许某个实现者仅仅需要打印功能但缺强制需要实现扫描的函数实现。
修复:将所有接口拆分开
struct IPrinter
{virtual void print(vector<Document*> docs) = 0;
}struct IScanner
{virtual void scan(vector<Document*> docs) = 0;
}// 打印机或扫描仪可以单独继承
// 如果想要一个同时有打印扫描功能
struct IMachine : IPrinter, IScanner
{
}
1.5.5 依赖倒转原则19
反例:Reporting 模块依赖ILogger接口
class Reporting
{ILogger& logger; // 依赖就是将对方作为自己的参数引入
}
存在的问题:当Reporting同时依赖5个不同的接口?
第一部分 创建型设计模式
回顾C++中创建对象的方法
- 栈分配
- 堆分配 new
- unique指针
- shared指针
- weak指针
函数返回比字节大的对象:
Foo func(){return Foo{n};
}
第2章 构造器模式26
构造器模式主要关注复杂对象的创建过程,复杂对象指的是难以通过调用单行构造函数来创建的对象。
2.1 预想方案26
2.2 简单构造器27
struct HtmlBuilder
{void add_child(){}
}
build.add_child( "hello" );
2.3 流式构造器28
2.4 向用户传达意图28
2.5 Groovy风格的构造器30
2.6 组合构造器32
2.7 参数化构造器35
强制用户使用构造器
MailService ms;
ms.send_email([&](auto& eb){
eb.from("foo@bar.com")
.to("bar@baz.com");
});
2.8 构造器模式的继承性36
2.9 总结39
第3章 工厂方法和抽象工厂模式41
3.1 预想方案41
3.2 工厂方法43
3.3 工厂44
3.4 工厂方法和多态46
3.5 嵌套工厂47
3.6 抽象工厂48
3.7 函数式工厂50
3.8 对象追踪51
3.9 总结52
第4章 原型模式53
4.1 对象构建53
4.2 普通拷贝54
4.3 通过拷贝构造函数进行拷贝54
4.4 “虚”构造函数56
virtual Address* clone()
{return new Address{street, city, suite};
}
4.5 序列化58
4.6 原型工厂60
4.7 总结61
第5章 单例模式62
5.1 作为全局对象的单例模式62
5.2 单例模式的经典实现63
防止被显示构建的唯一方式仍旧是将其构造函数声明为私有的。
struct Database
{
protected:Database();
public:static Database& get(){static Database database;return database;}
}
线程安全
单例模式初始化是线程安全的,不会遇到创建两次数据的情况。
5.3 单例模式存在的问题65
5.3.1 每线程单例68
5.3.2 环境上下文70
5.3.3 单例模式与控制反转73
5.3.4 单态模式73
5.4 总结74
第二部分 结构型设计模式
第6章 适配器模式77
6.1 预想方案77
6.2 适配器79
6.3 临时适配器对象80
6.4 双向转换器82
6.5 总结83
第7章 桥接模式85
7.1 Pimpl模式85
7.2 桥接模式介绍87
7.3 总结89
第8章 组合模式90
8.1 支持数组形式的属性91
8.2 组合图形对象93
只依赖于一个接口
struct GraphicObject
{virtual void draw()=0;
};// 继承
struct Circle : GraphicObject
{void draw() override{cout << "Circle" << endl;}
};struct Group : GraphicObject
{void draw() override{cout << "Group" << endl;}vector<GraphicObject*> Object;
};Group root( "root" );
Circle c1;
root.object.push_back( &c1 );
8.3 神经网络95
8.3.1 封装组合模式97
8.3.2 概念上的改进98
8.3.3 概念和全局运算符99
8.4 组合模式的规范100
8.5 总结101
第9章 装饰器模式103
9.1 预想方案103
9.2 动态装饰器104
9.3 静态装饰器107
9.4 函数装饰器108
9.5 总结111
第10章 外观模式112
10.1 幻方生成器112
10.2 构建贸易终端116
10.2.1 高级终端117
10.2.2 “外观”体现在哪里118
10.3 总结119
第11章 享元模式120
11.1 用户名问题120
大量重复的用户名,占用空间。可以只存储一次名字,然后将存储指向具有该名字的用户指针。将姓名分成姓氏,名字更有意义。
11.2 Boost.Flyweight122
11.3 字符串的范围122
11.3.1 幼稚解法123
11.3.2 享元实现124
11.4 总结126
第12章 代理模式127
12.1 智能指针127
12.2 属性代理128
12.3 虚拟代理129
12.4 通信代理131
12.5 值代理133
12.6 总结135
第三部分 行为型设计模式
第13章 职责链模式138
13.1 预想方案138
13.2 指针链139
13.3 代理链141
13.4 总结144
第14章 命令模式145
14.1 预想方案145
14.2 实现命令模式146
14.3 撤销操作147
14.4 复合命令149
14.5 命令查询分离152
14.6 总结154
第15章 解释器模式155
15.1 解析整数155
15.2 数值表达式求值156
15.2.1 词法分析157
15.2.2 语法分析159
15.2.3 使用词法分析器和
语法分析器161
15.3 使用Boost.Spirit解析161
15.3.1 抽象语法树162
15.3.2 语法分析器163
15.3.3 打印器164
15.4 总结164
第16章 迭代器模式166
16.1 标准库中的迭代器166
16.2 遍历二叉树168
16.3 使用协程的迭代171
16.4 总结172
第17章 中介者模式174
17.1 聊天室174
17.2 中介者与事件178
17.3 中介者服务总线180
17.4 总结181
第18章 备忘录模式182
18.1 银行账户182
18.2 撤销功能和恢复功能183
18.3 内存注意事项186
18.4 使用备忘录进行交互操作186
18.5 总结187
第19章 空对象模式188
19.1 预想方案188
19.2 shared_ptr不是空对象190
19.3 设计改进190
19.4 隐式空对象191
19.5 与其他模式的交互191
19.6 总结192
第20章 观察者模式193
20.1 属性观察器193
20.2 Observer194
20.3 Observable195
20.4 连接观察者和被观察者196
20.5 依赖问题197
20.6 取消订阅与线程安全197
20.7 可重入性198
20.8 Boost.Signals2中的观察者模式200
20.9 视图201
20.10 总结203
第21章 状态模式204
21.1 状态驱动的状态转换204
21.2 设计状态机207
21.3 基于开关的状态机209
21.4 Boost.MSM状态机211
21.5 总结214
第22章 策略模式215
22.1 动态策略216
// 定义策略抽象类
struct ListStrategy
{virtual void add_list_item()
}struct MarkdownListStrategy : ListStrategy
{void add_list_item() override
}struct TextProcessor
{void append_list(){list_strategy->add_list_item(); }
private:
unique_ptr list_stratgy;
}void set_output_format(format)
{switch(format) 切换策略case markdownlist_strategy = ;
}TextProcessor tp.set_output_format(markdown)// 使用时设置策略
22.2 静态策略219
22.3 总结219
第23章 模板方法模式221
策略模式和模板方法非常相似。
23.1 游戏模拟221
定义游戏模板
class Game
{void run() // 玩游戏模板{start();while(!have_winner())take_turn();cout << "Player" << get_winner() << "wins.";}protected:virtual void start() = 0;// 具体类去实现virtual void have_winner() = 0;virtual void take_turn() = 0;virtual void get_winner() = 0;
}
23.2 函数式模板方法223
23.3 总结224
练习:
void Uplink::sndTcp()
{pkt_len = char ipheader + udp header + icmp headeriphdr *ip = pkttcphdr *tcp = ip+1build_headersend_pkt
}
第24章 访问者模式226
24.1 侵入式访问者227
24.2 反射式打印组件228
24.3 什么是分发230
24.4 经典访问者231
24.5 非循环访问者234
24.6 std::variant和std::visit236
24.7 总结237