You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 19 | 设计模式C++与设计模式有啥关系?
你好我是Chrono。
今天,我们进入最后的“总结”单元,把前面学到的这些知识上升到“理论结合实践”的高度,做个归纳整理。我们先来了解一下设计模式和设计原则,然后再把理论“落地”,综合利用所有知识点,设计并开发出一个实际的服务器应用。
你可能会问了我们这是个C++的课程,为什么还要专门来讲设计模式呢?
我觉得设计模式是一门通用的技术是指导软件开发的“金科玉律”它不仅渗透进了C++语言和库的设计当然也包括其他编程语言而且也是成为高效C++程序员必不可缺的“心法”和“武器”。
掌握了它们理解了语言特性、库和工具后面的设计思想你就可以做到“知其然更知其所以然”然后以好的用法为榜样以坏的用法为警示扬长避短从而更好地运用C++。
所以我把我这些年的实践经验进行了提炼和总结糅合成了两节课帮你快速掌握并且用好设计模式写出高效、易维护的代码。这节课我会先讲一讲学好设计模式的核心方法下节课我们再讲在C++里具体应用了哪些设计模式。
## 为什么要有设计模式?
虽然C++支持多范式编程但面向对象毕竟还是它的根基而且面向对象编程也通用于当前各种主流的编程语言。所以学好、用好面向对象对于学好C++来说,非常有用。
但是,想要得到良好的面向对象设计,并不是一件容易的事情。
因为每个人自身的能力、所在的层次、看问题的角度都不同,仅凭直觉“对现实建模”,很有可能会生成一些大小不均、职责不清、关系混乱的对象,最后搭建出一个虽然可以运行,但却难以理解、难以维护的系统。
所以,设计模式就是为此而生的。
它系统地描述了一些软件开发中的常见问题、应用场景和对应的解决方案,给出了**专家级别的设计思路和指导原则**。
按照设计模式去创建面向对象的系统,就像是由专家来“手把手”教你,不能说绝对是“最优解”,但至少是“次优解”。
而且,在应用设计模式的过程中,你还可以从中亲身体会这些经过实际证明的成功经验,潜移默化地影响你自己思考问题的方式,从长远来看,学习和应用设计模式能够提高你的面向对象设计水平。
## 学习、理解设计模式才能用好面向对象的C++
经典的《设计模式》一书里面介绍了23个模式并依据设计目的把它们分成了三大类创建型模式、结构型模式和行为模式。
这三类模式分别对应了开发面向对象系统的三个关键问题:**如何创建对象、如何组合对象,以及如何处理对象之间的动态通信和职责分配**。解决了这三大问题,软件系统的“架子”也就基本上搭出来了。
![](https://static001.geekbang.org/resource/image/75/94/7568cdf68c4922e41188cd274a01c294.jpg)
23个模式看起来好像不是很多但它们的内涵和外延都是非常丰富的不然也不会有数不清的论文、书刊研究它们了所以我们要从多角度、多方面去评价、审视模式。
那该怎么做才好呢?
你可以看一下《设计模式》的原书,它用了一个很全面的体例来描述模式,包括名称、别名、动机、结构、示例、效果、相关模式,等等。
虽然显得有点琐碎、啰唆,但我们必须要承认,这种严谨、甚至是有些刻板的方式能够全方位、无死角地介绍模式,强迫你从横向、纵向、深层、浅层、抽象、具体等各个角度来研究、思考。只有在这个过程中,你才能真正掌握设计模式的内核。
模式里的结构和实现方式直接表现为代码,可能是最容易学习的部分,但我认为,其实这些反而是最不重要的。
**你更应该去关注它的参与者、设计意图、面对的问题、应用的场合、后续的效果等代码之外的部分,它们通常比实现代码更重要**。
因为代码是“死”的只能限定由某几种语言实现而模式发现问题、分析问题、解决问题的思路是“活”的适用性更广泛这种思考“What、Where、When、Why、How”并逐步得出结论的过程才是设计模式专家经验的真正价值。
理解了这些内容我们就可以应用在C++面向对象编程里了。下节课我会具体给你讲一讲在C++里,这些该怎么用。
## 学习、理解设计原则才能用好多范式的C++
可能你在学习设计模式的时候还是有些困惑,设计模式是专家经验的总结不假,但专家们是如何察觉、发现、探索出这些模式的呢?
而且模式真的完全只是“模式”、固定的“套路”,有没有什么更一般的思想来指导我们呢?换句话说,有没有“设计‘设计模式’的模式”呢?
嗯,这个真的有(笑)。
其实,这些更高层次的指导思想你可能也听说过,它们被通称为“设计原则”。
最常用有5个原则也就是常说的“SOLID”。
1. SRP单一职责Single ResponsibilityPrinciple
2. OCP开闭Open Closed Principle
3. LSP里氏替换Liskov Substitution Principle
4. ISP接口隔离Interface-Segregation Principle
5. DIP依赖反转有的时候也叫依赖倒置Dependency Inversion Principle
不过可能是因为我最先接触、研究的是设计模式,所以后来再看到这些原则的时候,“认同感”就没有那么强烈了。
虽然它们都说得很对,但没有像设计模式那样给出完整、准确的论述。所以,我觉得它们有点“飘”,缺乏可操作性,在实践中不好把握使用的方式。
但另一方面,这些原则也确实提炼出了软件设计里最本质、最基本的东西,就好像是欧几里得五公设、牛顿三定律一样,初看上去似乎很浅显直白,但仔细品品,就会发现,可以应用到任何系统里,所以了解它们还是很有必要的。
下面我就来讲讲对设计原则的一些理解和看法再结合C++和设计模式帮你来加深认识进而在C++里实际用好它们。
第一个,**单一职责原则**,简单来说就是“**不要做多余的事**”,更常见的说法就是“**高内聚低耦合**”。在设计类的时候,要尽量缩小“粒度”,功能明确单一,不要设计出“大而全”的类。
使用单一职责原则,经常会得到很多“短小精悍”的对象,这时候,就需要应用设计模式来组合、复用它们了,比如,使用工厂来分类创建对象、使用适配器、装饰、代理来组合对象、使用外观来封装批量的对象。
单一职责原则的一个反例是C++标准库里的字符串类string参见[第11讲](https://time.geekbang.org/column/article/242603)),它集成了字符串和字符容器的双重身份,接口复杂,让人无所适从(所以,我们应该只把它当作字符串,而把字符容器的工作交给`vector<char>`)。
第二个是**开闭原则**,它也许是最“模糊”的设计原则了,通常的表述是“**对扩展开放,对修改关闭**”,但没有说具体该怎么做,跟没说一样。
我觉得,你可以反过来理解这个原则,在设计类的时候问一下自己,这个类封装得是否足够好,是否可以不改变源码就能够增加新功能。如果答案是否定的(要改源码),那就说明违反了开闭原则。
**应用开闭原则的关键是做好封装**,隐藏内部的具体实现细节,然后开放足够的接口,这样外部的客户代码就可以只通过接口去扩展功能,而不必侵入类的内部。
你可以在一些结构型模式和行为模式里找到开闭原则的“影子”:比如桥接模式让接口保持稳定,而另一边的实现任意变化;又比如迭代器模式让集合保持稳定,改变访问集合的方式只需要变动迭代器。
C++语言里的final关键字[第5讲](https://time.geekbang.org/column/article/235301))也是实践开闭原则的“利器”,把它用在类和成员函数上,就可以有效地防止子类的修改。
第三个原则是**里氏替换原则**,意思是**子类必须能够完全替代父类**。
这个原则还是比较好理解的就是说子类不能改变、违反父类定义的行为。像在第5讲里说的正方形、鸟类的例子它们就是违反了里氏替换原则。
不过因为C++支持泛型编程而且我也不建议多用继承所以在C++里你只要了解一下它就好。
第四个是**接口隔离原则**,它和单一职责原则有点像,但侧重点是对外的接口而不是内部的功能,目标是**尽量简化、归并给外界调用的接口**,避免写出大而不当的“面条类”。
大多数结构型模式都可以用来实现接口隔离,比如,使用适配器来转换接口,使用装饰模式来增加接口,使用外观来简化复杂系统的接口。
第五个原则是**依赖反转原则**,个人觉得是一个比较难懂的原则,我的理解是**上层要避免依赖下层的实现细节,下层要反过来依赖上层的抽象定义**,说白了,大概就是“解耦”吧。
模板方法模式可以算是比较明显的依赖反转的例子,父类定义主要的操作步骤,子类必须遵照这些步骤去实现具体的功能。
如果单从“解耦”的角度来理解的话,存在上下级调用关系的设计模式都可以算成是依赖反转,比如抽象工厂、桥接、适配器。
![](https://static001.geekbang.org/resource/image/c2/ca/c257c85fc3c5aefbfcdfb8d3ecb4b9ca.jpg)
除了SOLID这五个之外我觉得还有两个比较有用DRYDont Repeate Yourself和KISSKeep It Simple Stupid
它们的含义都是要让代码尽量保持简单、简洁避免重复的代码这在C++里可以有很多方式去实现比如用宏代替字面值用lambda表达式就地定义函数多使用容器、算法和第三方库。
## 小结
好了,今天就到这里吧,我从比较“宏观”的层面说了设计模式和设计原则。
其实这些就是对我们实际开发经验的高度浓缩和总结。理解掌握了这些经验你就会始终保持着清醒的头脑在写C++代码的过程中有意识地去发现、应用模式,设计出好的结构,对坏的代码进行重构。
小结一下这节课的要点:
1. 面向对象是主流编程范式,使用设计模式可以比较容易地得到良好的面向对象设计;
2. 经典的设计模式有23个分成三大类创建型模式、结构型模式和行为模式
3. 应该从多角度、多方面去研究设计模式,多关注代码之外的部分,学习解决问题的思路;
4. 设计原则是设计模式之上更高层面的指导思想,适用性强,但可操作性弱,需要多在实践中体会;
5. 最常用的五个设计原则是“SOLID”此外还有“DRY”和“KISS”。
不过,我还要特别提醒你,设计模式虽然很好,但它绝不是包治百病的“灵丹妙药”。如果不论什么项目都套上设计模式,就很容易导致过度设计,反而会增加复杂度,僵化系统。
对于我们C++程序员来说更是要清楚地认识到这一点因为在C++里,不仅有面向对象编程,还有泛型编程和函数式编程等其他范式,所以领会它的思想,在恰当的时候改用模板/泛型/lambda来替换“纯”面向对象才是使用设计模式的最佳做法。
## 课下作业
最后是课下作业时间,给你留两个思考题:
1. 你觉得使用设计模式有什么好处?
2. 你是怎么理解SOLID设计原则的哪个对你最有指导意义
欢迎你在留言区写下你的思考和答案,如果觉得今天的内容对你有所帮助,也欢迎分享给你的朋友。我们下节课见。
![](https://static001.geekbang.org/resource/image/36/c0/363f39702c4f6788b6b56d96881650c0.png)