C++设计模式
面向对象编程的一些术语:
延迟到子类: 定义一个虚函数,让子类来重写获实现他。这其实就是支持子类去 变化。
扩展: 继承 + 多态,即,子类继承基类 并对其虚函数进行 override
依赖: 一般指编译时依赖,如:A依赖B,那么在编译的时候,编译器需要先编译 B,才能再编译 A。本篇指的一般是编译时依赖,如果是运行时依赖会特别说明。
1、面上对象设计的8大原则
- 依赖倒置:
隔离变化。高层模块(需要稳定)不应该依赖于低层模块(是易变化的),二者都应该依赖于抽象(稳定的)。
抽象(稳定)不应该依赖于实现细节(易变化的),实现细节应该依赖于抽象(稳定)。 - 开放封闭:
类模块应该是可扩展(增加)的,但是不可修改的。 - 单一职责:
一个类应该仅有一个引起它变化的原因。变化的方向隐含着类的的责任。(类不能太臃肿,过多的责任会把类往不同方向撕扯) - 替换原则:
子类能够替换基类,所有使用父类的地方把子类传过去也能用。 - 接口隔离:
接口应该尽量小。即,有必要的时候才将方法放入 public - 优先使用对象组合,而不是类继承。
- 封装变化点:
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响 - 针对接口编程,而不是针对实现编程 。(客户只需知道接口即可,无需获取变量类型)
管理变化,提高复用
2、设计模式
2.1设计模式 要做的事情及其意义
在软件的 变化 和 稳定 中间寻找隔离点,从而分离他们,从而更好地管理变化。
2.2 如何很好的应用设计模式
在应用设计模式时,关键就是要区分出软件体系结构中 哪些是稳定的,哪些是变化的。
要 动态地看 一个软件的结构层次。
2.3 真正学习设计模式时
拿到一张设计模式的类图,重点要区分出类图中哪些是稳定的,哪些是变化的。
3、模板方法模式
早绑定:一个晚的东西调用一个早(库)的东西。
晚绑定:反过来。(由库来调用我们写的东西。)这是该模式在代码中的体现形式。
4、策略模式
表现形式: 多态。定义一个基类,我们可以扩展其子类并重写其虚函数,从而实现变化。而调用这些方法的接口不用变。
代码中如果出现了if else
或switch case
的这种结构,通常是使用策略模式的特征,此时你就要考虑一下未来会不会增加其他的else if
分支,如果有可能那么此时应该使用策略模式。但如果if else
里判断的是星期几,情况固定,那么就不必使用策略模式。
“当你看到if else
时,你就闻到了代码中的坏味道”——马丁·弗勒
5、观察者模式
摘自:https://www.midlane.top/wiki/pages/viewpage.action?pageId=26181878
6、装饰模式
表现形式: 组合 + 多态。
摘自:https://www.midlane.top/wiki/pages/viewpage.action?pageId=26181888
装饰模式应用的要点在于解决“主体类在多个方向上的扩展功能”
装饰模式达到的效果:各个类在编译时是各司其职,但可以通过编写代码实现在运行时随意装配组合功能(但编译时,没有实现这种功能的类)。
7、桥模式
桥模式和装饰模式很相似,它们的区别是继承体系的数量不同。装饰模式只有一个继承体系;而桥模式有两个继承体系,他们之间由一个指针联系起来,这个指针就像桥一样。
当一个类中有多个非常强的变化维度时,就把属于某个变化维度的成员打包在一起单独作为一个基类,并且引一个指针指向它。
8、工厂方法模式
解决的问题: 由于需求的变化,需要创建的对象的具体类型经常变化。我们需要避免直接new
一个对象,从而避免这种紧耦合。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
9、抽象工厂模式
和工厂方法模式很像都是用来避免在创建对象时的紧耦合。但抽象工厂解决的是创建【一系列相互依赖的对象】的创建工作。
可以将工厂方法 看作 抽象工厂的一个特例,即,要创建的对象只有一个,而不是一个系列。
10、原型模式
使用场景和工厂模式很相似,都是用来避免在创建对象时的紧耦合。
什么时候使用该原型:当使用工厂方法创建对象所需要的步骤很复杂,且 需要考虑 并 保留对象的中间状态时,就需要使用原型模式。
11、构建器模式
该模式和模板方法模式很像,但构建器模式的思考方式更加细化,他是从构建对象的角度来看的。
构建器模式将复杂对象的构建与其表示相分离,使得同样的构建流程(稳定)可以创建不同的表示(变化)。
12、单例模式
单例模式解决的不是抽象问题,而是性能问题。
单例模式保证某个类只有一个实例,并提供一个该实例的全局访问点。
单例模式的实现一般有两种方式:
懒汉模式: 第一次用到类的实例时才实例化。
饿汉模式: 单例类定义的时候就进行实例化。
更多懒汉模式和饿汉模式可以参考这里。
普通的单例模式实现方法是非线程安全的,因此多线程编程中使用单例模式涉及到锁,这里牵扯到很多问题。所以先记几篇博客:
Boost线程库学习笔记
boost::scope_lock简要总结
单例模式和线程安全
单例模式和线程安全
13、享元模式
单例模式解决的也不是抽象问题,而是性能问题。即,当需要创建很多对象时,我们让他们共享一个对象,而不是真的去创建这么多对象。
运用共享技术有效地支持大量细粒度的对象。
使用享元模式创建的对象一般都是只读的,否则共享就不成立了。
14、门面模式
将系统内的变化圈起来,对系统外提供统一的接口访问系统内。
对外松耦合,对内高内聚。
15、代理模式
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
增加间接层,在间接层中实现一些不为外界所知的功能。在具体实现中,间接层中需要做的工作可能非常庞大。
16、适配器模式
将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
17、中介者模式
(李建忠C++设计模式第17集17分10秒处讲解了四种接口隔离模式的区别)
18、状态模式
19、备忘录模式
备忘录模式的思想是:在不破坏封装性的前提下,捕获一个对象的内部状态(即,不能让外部知道对象的实现细节),并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
如今该模式的实现方式已经基本脱离了面向对象编程,而是采用序列化等方式。所以,对于该模式我们只需知道其思想即可,而实现方式另外去学习。
21、组合模式
组合模式将对象组合成 树形结构 以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组件对象的使用具有一致性(稳定)。
组合模式采用树形结构来实现普通存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器。
22、迭代器模式
在C++中该设计模式已经被STL标准库中的迭代器iterator
所取代(用模板+适配器模式实现),而在其他语言中遍历不同类型的容器确实是用迭代器模式实现的。
23、职责链模式
可以做到在运行时辨析哪个处理者可以处理这个请求。
不过该模式在今天也基本用的很少了。
24、命令模式
把行为抽象为对象(类似于函数对象)。
目前,在C++中,函数对象已经基本取代了命令模式,但命令模式在Java等其它语言得到了广泛的应用。
25、访问器模式
表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
弄清楚上图中哪些部分可变,哪些部分不可变很重要。
访问器模式虽然能够动态地为整个对象体系中的所有类添加新方法,但是,使用该模式的前提条件也是很苛刻的。所以,在实际开发中很少有能用该模式的情景。
26、解析器模式
应用场景:解析类似于 “肆仟叁佰伍拾陆”,这样的文法,将其转换成数字表示,就可以使用解析器模式。
解析器模式比较是和简单的文法表示,对于复杂的文法表示,解析器模式会产生很大的类层次结构。目前,解析器模式用的也比较少。
27、设计模式总结
27.1 C++对象模型
指针指向多态对象是松耦合对象模型基础。
27.2 关注变化点和稳定点
一般项目中变化点和稳定点的数量呈正态分布。