<audio id="audio" title="19 | 设计模式（上）：C++与设计模式有啥关系？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/be/612668ecffcd52db213b88b3ad536dbe.mp3"></audio>

你好，我是Chrono。

今天，我们进入最后的“总结”单元，把前面学到的这些知识上升到“理论结合实践”的高度，做个归纳整理。我们先来了解一下设计模式和设计原则，然后再把理论“落地”，综合利用所有知识点，设计并开发出一个实际的服务器应用。

你可能会问了：我们这是个C++的课程，为什么还要专门来讲设计模式呢？

我觉得，设计模式是一门通用的技术，是指导软件开发的“金科玉律”，它不仅渗透进了C++语言和库的设计（当然也包括其他编程语言），而且也是成为高效C++程序员必不可缺的“心法”和“武器”。

掌握了它们，理解了语言特性、库和工具后面的设计思想，你就可以做到“知其然，更知其所以然”，然后以好的用法为榜样，以坏的用法为警示，扬长避短，从而更好地运用C++。

所以，我把我这些年的实践经验进行了提炼和总结，糅合成了两节课，帮你快速掌握，并且用好设计模式，写出高效、易维护的代码。这节课，我会先讲一讲学好设计模式的核心方法，下节课，我们再讲在C++里具体应用了哪些设计模式。

## 为什么要有设计模式？

虽然C++支持多范式编程，但面向对象毕竟还是它的根基，而且，面向对象编程也通用于当前各种主流的编程语言。所以，学好、用好面向对象，对于学好C++来说，非常有用。

但是，想要得到良好的面向对象设计，并不是一件容易的事情。

因为每个人自身的能力、所在的层次、看问题的角度都不同，仅凭直觉“对现实建模”，很有可能会生成一些大小不均、职责不清、关系混乱的对象，最后搭建出一个虽然可以运行，但却难以理解、难以维护的系统。

所以，设计模式就是为此而生的。

它系统地描述了一些软件开发中的常见问题、应用场景和对应的解决方案，给出了**专家级别的设计思路和指导原则**。

按照设计模式去创建面向对象的系统，就像是由专家来“手把手”教你，不能说绝对是“最优解”，但至少是“次优解”。

而且，在应用设计模式的过程中，你还可以从中亲身体会这些经过实际证明的成功经验，潜移默化地影响你自己思考问题的方式，从长远来看，学习和应用设计模式能够提高你的面向对象设计水平。

## 学习、理解设计模式，才能用好面向对象的C++

经典的《设计模式》一书里面介绍了23个模式，并依据设计目的把它们分成了三大类：创建型模式、结构型模式和行为模式。

这三类模式分别对应了开发面向对象系统的三个关键问题：**如何创建对象、如何组合对象，以及如何处理对象之间的动态通信和职责分配**。解决了这三大问题，软件系统的“架子”也就基本上搭出来了。

<img src="https://static001.geekbang.org/resource/image/75/94/7568cdf68c4922e41188cd274a01c294.jpg" alt="">

23个模式看起来好像不是很多，但它们的内涵和外延都是非常丰富的，不然也不会有数不清的论文、书刊研究它们了，所以，我们要从多角度、多方面去评价、审视模式。

那该怎么做才好呢？

你可以看一下《设计模式》的原书，它用了一个很全面的体例来描述模式，包括名称、别名、动机、结构、示例、效果、相关模式，等等。

虽然显得有点琐碎、啰唆，但我们必须要承认，这种严谨、甚至是有些刻板的方式能够全方位、无死角地介绍模式，强迫你从横向、纵向、深层、浅层、抽象、具体等各个角度来研究、思考。只有在这个过程中，你才能真正掌握设计模式的内核。

模式里的结构和实现方式直接表现为代码，可能是最容易学习的部分，但我认为，其实这些反而是最不重要的。

**你更应该去关注它的参与者、设计意图、面对的问题、应用的场合、后续的效果等代码之外的部分，它们通常比实现代码更重要**。

因为代码是“死”的，只能限定由某几种语言实现，而模式发现问题、分析问题、解决问题的思路是“活”的，适用性更广泛，这种思考“What、Where、When、Why、How”并逐步得出结论的过程，才是设计模式专家经验的真正价值。

理解了这些内容，我们就可以应用在C++面向对象编程里了。下节课，我会具体给你讲一讲在C++里，这些该怎么用。

## 学习、理解设计原则，才能用好多范式的C++

可能你在学习设计模式的时候还是有些困惑，设计模式是专家经验的总结不假，但专家们是如何察觉、发现、探索出这些模式的呢？

而且模式真的完全只是“模式”、固定的“套路”，有没有什么更一般的思想来指导我们呢？换句话说，有没有“设计‘设计模式’的模式”呢？

嗯，这个真的有（笑）。

其实，这些更高层次的指导思想你可能也听说过，它们被通称为“设计原则”。

最常用有5个原则，也就是常说的“SOLID”。

1. SRP，单一职责（Single ResponsibilityPrinciple）；
1. OCP，开闭（Open Closed Principle）；
1. LSP，里氏替换（Liskov Substitution Principle）；
1. ISP，接口隔离（Interface-Segregation Principle）；
1. DIP，依赖反转，有的时候也叫依赖倒置（Dependency Inversion Principle）。

不过可能是因为我最先接触、研究的是设计模式，所以后来再看到这些原则的时候，“认同感”就没有那么强烈了。

虽然它们都说得很对，但没有像设计模式那样给出完整、准确的论述。所以，我觉得它们有点“飘”，缺乏可操作性，在实践中不好把握使用的方式。

但另一方面，这些原则也确实提炼出了软件设计里最本质、最基本的东西，就好像是欧几里得五公设、牛顿三定律一样，初看上去似乎很浅显直白，但仔细品品，就会发现，可以应用到任何系统里，所以了解它们还是很有必要的。

下面我就来讲讲对设计原则的一些理解和看法，再结合C++和设计模式，帮你来加深认识，进而在C++里实际用好它们。

第一个，**单一职责原则**，简单来说就是“**不要做多余的事**”，更常见的说法就是“**高内聚低耦合**”。在设计类的时候，要尽量缩小“粒度”，功能明确单一，不要设计出“大而全”的类。

使用单一职责原则，经常会得到很多“短小精悍”的对象，这时候，就需要应用设计模式来组合、复用它们了，比如，使用工厂来分类创建对象、使用适配器、装饰、代理来组合对象、使用外观来封装批量的对象。

单一职责原则的一个反例是C++标准库里的字符串类string（参见[第11讲](https://time.geekbang.org/column/article/242603)），它集成了字符串和字符容器的双重身份，接口复杂，让人无所适从（所以，我们应该只把它当作字符串，而把字符容器的工作交给`vector&lt;char&gt;`）。

第二个是**开闭原则**，它也许是最“模糊”的设计原则了，通常的表述是“**对扩展开放，对修改关闭**”，但没有说具体该怎么做，跟没说一样。

我觉得，你可以反过来理解这个原则，在设计类的时候问一下自己，这个类封装得是否足够好，是否可以不改变源码就能够增加新功能。如果答案是否定的（要改源码），那就说明违反了开闭原则。

**应用开闭原则的关键是做好封装**，隐藏内部的具体实现细节，然后开放足够的接口，这样外部的客户代码就可以只通过接口去扩展功能，而不必侵入类的内部。

你可以在一些结构型模式和行为模式里找到开闭原则的“影子”：比如桥接模式让接口保持稳定，而另一边的实现任意变化；又比如迭代器模式让集合保持稳定，改变访问集合的方式只需要变动迭代器。

C++语言里的final关键字（[第5讲](https://time.geekbang.org/column/article/235301)）也是实践开闭原则的“利器”，把它用在类和成员函数上，就可以有效地防止子类的修改。

第三个原则是**里氏替换原则**，意思是**子类必须能够完全替代父类**。

这个原则还是比较好理解的，就是说子类不能改变、违反父类定义的行为。像在第5讲里说的正方形、鸟类的例子，它们就是违反了里氏替换原则。

不过，因为C++支持泛型编程，而且我也不建议多用继承，所以在C++里你只要了解一下它就好。

第四个是**接口隔离原则**，它和单一职责原则有点像，但侧重点是对外的接口而不是内部的功能，目标是**尽量简化、归并给外界调用的接口**，避免写出大而不当的“面条类”。

大多数结构型模式都可以用来实现接口隔离，比如，使用适配器来转换接口，使用装饰模式来增加接口，使用外观来简化复杂系统的接口。

第五个原则是**依赖反转原则**，个人觉得是一个比较难懂的原则，我的理解是**上层要避免依赖下层的实现细节，下层要反过来依赖上层的抽象定义**，说白了，大概就是“解耦”吧。

模板方法模式可以算是比较明显的依赖反转的例子，父类定义主要的操作步骤，子类必须遵照这些步骤去实现具体的功能。

如果单从“解耦”的角度来理解的话，存在上下级调用关系的设计模式都可以算成是依赖反转，比如抽象工厂、桥接、适配器。

<img src="https://static001.geekbang.org/resource/image/c2/ca/c257c85fc3c5aefbfcdfb8d3ecb4b9ca.jpg" alt="">

除了SOLID这五个之外，我觉得还有两个比较有用：DRY（Don’t Repeate Yourself）和KISS（Keep It Simple Stupid）。

它们的含义都是要让代码尽量保持简单、简洁，避免重复的代码，这在C++里可以有很多方式去实现，比如用宏代替字面值，用lambda表达式就地定义函数，多使用容器、算法和第三方库。

## 小结

好了，今天就到这里吧，我从比较“宏观”的层面说了设计模式和设计原则。

其实这些就是对我们实际开发经验的高度浓缩和总结。理解掌握了这些经验，你就会始终保持着清醒的头脑，在写C++代码的过程中有意识地去发现、应用模式，设计出好的结构，对坏的代码进行重构。

小结一下这节课的要点：

1. 面向对象是主流编程范式，使用设计模式可以比较容易地得到良好的面向对象设计；
1. 经典的设计模式有23个，分成三大类：创建型模式、结构型模式和行为模式；
1. 应该从多角度、多方面去研究设计模式，多关注代码之外的部分，学习解决问题的思路；
1. 设计原则是设计模式之上更高层面的指导思想，适用性强，但可操作性弱，需要多在实践中体会；
1. 最常用的五个设计原则是“SOLID”，此外，还有“DRY”和“KISS”。

不过，我还要特别提醒你，设计模式虽然很好，但它绝不是包治百病的“灵丹妙药”。如果不论什么项目都套上设计模式，就很容易导致过度设计，反而会增加复杂度，僵化系统。

对于我们C++程序员来说，更是要清楚地认识到这一点，因为在C++里，不仅有面向对象编程，还有泛型编程和函数式编程等其他范式，所以领会它的思想，在恰当的时候改用模板/泛型/lambda来替换“纯”面向对象，才是使用设计模式的最佳做法。

## 课下作业

最后是课下作业时间，给你留两个思考题：

1. 你觉得使用设计模式有什么好处？
1. 你是怎么理解SOLID设计原则的？哪个对你最有指导意义？

欢迎你在留言区写下你的思考和答案，如果觉得今天的内容对你有所帮助，也欢迎分享给你的朋友。我们下节课见。

<img src="https://static001.geekbang.org/resource/image/36/c0/363f39702c4f6788b6b56d96881650c0.png" alt="">
