C++ 设计模式入门
更新中🔔
设计模式简介
设计模式:可复用面向对象软件的基础,可复用是设计模式的目的。
深入理解面向对象
- 向下:三大特性
- 封装:隐藏内部实现
- 继承:复用现有代码
- 多态:改写对象行为
- 向上:抽象的设计意义,使用三大特性来表达世界
设计模式的 6 大设计原则
- 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
- 开放封闭原则:软件实体可以扩展,但是不可修改。即面对需求,对程序的改动可以通过增加代码来完成,但是不能改动现有的代码。
- 里氏代换原则:一个软件实体如果使用的是一个基类,那么一定适用于其派生类。即在软件中 ,把基类替换成派生类,程序的行为没有变化。
- 依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要针对实现编程。
- 迪米特原则:如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。
- 接口隔离原则:每个接口中不存在派生类用不到却必须实现的方法,如果不然,就要将接口拆分,使用多个隔离的接口。
设计模式分类
- 创造型模式:单例模式、工厂模式、建造者模式、原型模式
- 结构型模式:适配器模式、桥接模式、外观模式、组合模式、装饰模式、享元模式、代理模式
- 行为型模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式
从封装变化的角度对设计模式分类
常见的几种设计模式
- 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式
- 观察者模式:定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,主题对象发生变化时,会通知所有的观察者,使他们能够更新自己。
- 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成派生类更为灵活。
组件协作
观察者模式
动机
- 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
模式定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《 设计模式》 GoF
结构
例子✨✨
文件分割器:将一个大文件分割为几个小文件,添加一个进度条。
- 在 FileSplitter 类中添加具体的通知控件进度条类 ProgressBar
- 违背了依赖倒置原则,进度条的更新依赖 ProgressBar 的内部实现
- 进度通知的展现方式可能不同:GUI界面、控制台等
- 观察者模式
- 添加抽象的通知机制 IProgress(接口),不耦合界面
- 观察者类继承 IProgress 接口,去定义具体的进度条更新 DoProgress(观察者和进度条是紧耦合的)
- 如果有多个观察者,则将添加观察者列表,添加 add 和 remove 方法
要点总结
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
单一职责
装饰模式
动机(Motivation)
- 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性; 并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代 码 & 减少子类个数)。 ——《设计模式》GoF
结构
例子✨✨
IO流(文件流、网络流、内存流)的业务操作(Read、Seek、Write),添加额外的加密、缓冲操作
- 继承
问题:类的规模急剧增加,但加密、缓冲操作相同,不同的只有流的业务操作,代码大量重复
- 装饰模式
1 |
|
要点总结
- 通过采用组合而非继承的手法, Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。 但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
对象创建
工厂方法模式
动机
- 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
- 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
模式定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。 ——《设计模式》GoF
结构
例子✨✨
文件分割器,未来变化的需求:多种文件(二进制文件、文本文件、图片文件、视频文件)
定义抽象分割器类 ISplitter ,多种文件分割器继承这个抽象分割器类
违背依赖倒置原则,主界面 MainForm 类依赖于具体分割器类的构造函数(细节)
工厂方法模式
定义SplitterFactory分割器工厂基类,声明CreateSplitter接口,具体的分割器工厂继承分割器工厂基类类,实现具体CreateSplitter,MainForm构造时使用一个基类的指针接受具体的分割器工厂指针,调用分割器工厂类的CreateSplitter方法创建具体的分割器对象
要点总结
- Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
- Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
- Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
抽象工厂模式
动机
- 在软件系统中,经常面临着“一系列相互依赖的对象工作”;同时,由于需求的变化,往往存在更多系列对象的创建工作。
- 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合。
模式定义
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。 ——《设计模式》GoF
结构
例子✨✨
数据库一系列操作 DBConnection、DBCommand、DBReader,多种数据库SQL、Oracle等
工厂方法模式
3个具有相关性的工厂
抽象工厂模式
将3个工厂合并为一个工厂,内部提供创建一系列的相关对象的接口
要点总结
- 如果没有应对”多系列对象创建“的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂即可。
- ”系列对象“指的是在某一个特定系列的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
- Abstract Factory模式主要在于应用”新系列“的需求变动。其缺点在与难以应对”新对象“的需求变动。
对象性能
单例模式
动机(Motivation)
- 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
- 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
- 这应该是类设计者的责任,而不是使用者的责任。
模式定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ——《设计模式》GoF
结构
实现✨✨
1 |
|
要点总结
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初中违背。
- 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现(编译器内存读写reorder不安全,volatile,std::atomic标准库)。
数据结构
职责链模式
动机(Motivation)
- 一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接收者,如果显式指定,将必不可少地带来请求发送者与接收者的紧耦合。
- 如何使请求的发送者不需要指定具体的接收者?让请求的接收者自己在运行时决定来处理请求,从而使两者解耦。
模式定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ——《设计模式》GoF
结构
要点总结
- 应用于”一个请求可能有多个接受者,但是最后真正的接受者只有一个“,这时候请求发送者与接受者有可能出现”变化脆弱“的症状,职责链解耦。
- 有些过时。