(1·设计模式分类、单例、观察者模式)
设计模式
- 理解:算法是解决特定问题的模板,设计模式是优化架构的模板
- 抽象:将通用的数据和方法提取出来的过程(房子的设计图纸)
- 封装:将抽象出来的数据和方法封装为类/将设置成员访问权限,对外隐藏内部实现细节(一个房子)
- 遵守高内聚,低耦合(设计原则)
- 高内聚:模块内部数据/功能相关程度的高
- 低耦合:模块/对象之间的依赖关系弱,修改一个模块 不会影响/不需要修改 其他模块的内部逻辑
- 六大基本原则(设计原则)
- 开闭原则、对象应对扩展开放,对修改关闭,在不修改现有代码的情况下添加新的功能,通常用抽象类实现
- 单一职责原则、一个类应该只负责一项职责
- 里氏替换原则、派生类可以扩展基类的功能,但不能改基类原有的功能
- 接口隔离原则、接口应该被细分为更小的、更具体的接口,避免在依赖该接口时依赖其不需要的方法,
- 依赖倒置原则、通过接口或抽象类来进行编程,而不是直接依赖于具体的实现类,抽象层和顶层不应该改变,底层可以改变
- 迪米特法则、一个对象应当对其他对象有尽可能少的了解,只与直接类通信,不与间接类通信
- 作用:(形成优秀的架构)
- 易扩展(积木组合了模块,方便添加修饰,和修改)
- 易复用(积木组合了模块,方便拷贝使用)
- 易组合(积木零件的组合方式)
- 简化使用(积木组合了模块,方便使用)
- 提高安全性(积木组合了模块,并不暴漏内部实现方式,防止破坏)
- 解耦:将原本相互依赖的模块/对象分离(把不好的结构优化)
- 重构:在不改变现有功能情况下,改善其内部结构的过程(让房子更结实)
- 分类:
- 创建型:提供创建对象的模板
- 结构型:提供组装成较大的结构的模板
- 行为型:提供对象间的高效沟通和职责委派的模板
单例——创建型
- 思想:创建整个程序中唯一 实例化(创建任意类型对象的过程)类对象(一切存储于内存的皆对象),并提供全局访问点
1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private:
// 私有的静态实例变量
static Singleton* instance = new Singleton();
// 私有的构造函数
Singleton() = default;
public:
// 公有的静态方法来获取实例
static Singleton* getInstance() {
return instance;
}
};
展开
饿汉:实例在类加载时就被创建,线程安全,浪费资源(如果从未使用该实例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton {
private:
// 私有的静态实例变量
static Singleton* instance;
// 私有的构造函数
Singleton() = default;
public:
// 公有的静态方法来获取实例
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;//类外定义
展开
懒汉模式:第一次使用时才创建,非线程安全(数据竞争),节省资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx; // 互斥锁
Singleton() = default;
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 首次创建应加锁
std::lock_guard<std::mutex> lock(mtx); // 加锁,阻塞其他线程,lock_guard保证作用域结束自动unlock
if (instance == nullptr) { // 第二次检查(确保唯一)
instance = new Singleton();
}
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;// 类外初始化
std::mutex Singleton::mtx;
展开
双重检查锁:在懒汉模式基础上,保证线程安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
private:
// 私有构造函数,防止外部直接创建对象
Singleton() = default;
public:
// 静态成员函数,用于获取单例实例的引用
static Singleton& getInstance() {
// 静态局部变量,在首次调用该函数时初始化
static Singleton instance;
// 返回单例实例的引用
return instance;
}
};
展开
双重检查锁优化:使用局部静态变量,c++中如果在静态局部变量正在初始化时,有其他线程同时进入该变量的声明语句,那么这些并发执行的线程会等待初始化完成,因此不会数据竞争
实现方式:
- 私有的构造函数:防止外部代码创建类对象,使得只能在内部创建(否则做不到全局唯一)
- 私有的静态实例变量:静态(否则静态方法无法访问),私有保证外部不能直接访问(封装原则)
- 公有的静态方法:静态保证可以不创建类实例就可以调用(否则必须创建类对象才能调用,陷入死循环),公有保证外部可以直接访问(封装原则,提供接口)
使用场景:管理系,系统
作用:节省资源
封装原则
- 封装原则:任何时候都不将成员变量设为 public,而通过两个接口读写
- get() 获取成员变量
- set() 设置成员变量
- 这样对外部隐藏内部实现细节,仅暴露必要的接口,提高安全性, 防止不正确的修改
工厂——创建型
- 思想:将创建对象流程集中封装到一个工厂类中
- 简单工厂:
- 实现
- 工厂类:包含一个创建方法,负责创建所有产品
- 抽象产品:负责一类产品, 包含所有产品通用的行为
- 具体产品:继承抽象产品
- 工厂负责创建所有产品,内部需要做if else的判断,在新建产品时工厂内部代码要更改,违背了开闭原则
- 实现
- 工厂方法:
- 实现
- 系统:包含一个创建方法
- 抽象工厂:包含一个创建方法
- 具体工厂:继承抽象工厂,包含一个创建方法,只负责创建一个具体产品
- 抽象产品:负责一类产品, 包含所有产品通用的行为
- 具体产品:继承抽象产品
- 将工厂修改为继承体系,让一个派生类负责一个具体产品的创建,在新建产品时不用修改工厂内部代码,而是扩展工厂派生类,这样遵守开闭原则
- 实现
- 抽象工厂
- 实现
- 抽象工厂: 包含一组(一类)创建方法
- 具体工厂:继承抽象工厂,包含一组创建方法,负责创建一组具体产品
- 抽象产品:定义多个,负责一类产品
- 具体产品: 继承抽象产品
- 如果有大量产品,每个产品被一个工厂派生类负责,会造成类爆炸,让一个工厂派生类负责一组产品创建,这组产品是有一定关系的
- 比如:椅子,沙发,柜子,古典风格,现代风格,其中古典风格,现代风格作为基类,椅子,沙发,柜子对它们都有继承,每个具体工厂负责椅子/沙发/柜子,并包含创建各个风格的具体产品函数,当新增一个具体产品,比如书桌,让它分别继承自古典风格,现代风格,再创建一个具体工厂继承它
- 实现
- 使用场景:生成器
- 作用:封装创建对象的过程,降低耦合
建造者——创建型
- 思想:将对象的构建过程分为多个步骤
- 使用场景:创建复杂的对象,场景
- 实现:
- 复杂产品:被构建的复杂对象, 属性集合,设置属性的接口
- 抽象建造者:包含构建产品各个步骤的方法,返回复杂产品的方法
- 具体建造者:继承抽象建造者
- 指导者:包含构建方法,按照一定的顺序调用具体建造者的方法来构建复杂产品
- 作用:将构建过程抽象出来,更易组合不同的对象
原型——创建型
- 思想:基于现有对象拷贝来创建新的对象,而不用重新初始化逻辑,资源加载,复杂计算……操作
- 作用:提高性能
- 使用场景:对象创建成本高,要创建大量相同的对象,创建大量敌人,粒子
- 实现
- 抽象原型:包含拷贝自身的方法
- 具体原型:包含拷贝自身的方法(深拷贝,自定义的拷贝构造,拷贝赋值)
组合——结构型
- 思想:将对象组合成树状结构来表示部分和整体的层次关系
- 使用场景:空间数据结构,技能树
- 实现方式:
- 组件:根节点,所有对象通用接口
- 合成:有枝节点,继承组件 / 使用指针(更灵活的方式),通常包含添加和获取子节点的方法
- 叶子:叶子节点,继承组件 / 使用指针
- 作用:统一的方式操作对象(递归)
适配器——结构型
- 思想:建立两个接口之间的桥梁
- 使用场景:当两个接口不兼容时,文件适配器
- 实现方式:
- 目标接口:使用的接口
- 适配器类:继承目标接口,持有被适配的引用,在继承的方法中调用被适配者方法
- 被适配者:需要被适配的接口
- 作用:解决被适配者和目标接口不兼容问题
代理——结构型
- 思想:一个对象充当另一个对象的接口
- 作用:控制对这个对象的访问,游戏数据
- 使用场景:用于在访问真实对象时引入一些额外的控制逻辑,如权限控制、延迟加载等
- 实现方式:
- 抽象主题:所有具体主题的通用方法
- 代理:继承抽象主题,包含主题的引用,控制访问逻辑
- 具体主题:继承抽象主题,最终要访问的对象
装饰——结构型
- 思想:给对象添加一些额外的功能,将被装饰对象和装饰对象分离,动态组合(3 + 3 < 3 * 3)
- 使用场景:给一个现有类添加附加功能,一个维度需要扩展时,装备,技能,材质
- 作用:灵活扩展功能,防止类爆炸
- 实现方式:
- 抽象组件: 所有具体组件的通用方法
- 具体组件: 继承抽象组件,是被装饰的对象
- 抽象装饰:继承抽象组件,持有组件的引用
- 具体装饰:继承抽象装饰
外观——结构型
- 思想:定义高层接口
- 使用场景:简化复杂接口
- 作用:提供简单易用的接口
- 实现方式:
- 外观:包含高层次接口
- 子系统:实现具体的功能
- 作用:
- 降低高层和底层耦合度
- 使得子系统更容易使用
- 隐藏了子系统的内部实现
桥接——结构型
- 思想:分解为高层维度和底层维度,两者通过组合拼接
- 使用场景:当一个类可以被分解为两个独立的维度,而且这两个维度都需要进行扩展时,材质和着色器
- 实现方式:
- 抽象高层维度:维护对底层维度的引用,提供调用方法
- 具体高层维度:继承抽象高层维度,负责对抽象底层维度的一个方法调用
- 抽象底层维度:所有具体底层维度通用方法
- 具体底层维度:抽象底层维度,提供具体实现
- 作用:防止类爆炸
享元——结构型
- 思想:对象被设计为可被重复使用的和少量可修改的,而不必每次都重新创建对象
- 使用场景:要创建大量相似的对象,实例化渲染,粒子
- 内部状态:不变的状态,存储在享元内部,通常在享元首次创建时传递
- 外部状态:变换的状态,存储在享元外部,通常在享元使用时通过参数传递给它
- 实现方式:
- 享元工厂:创建/获取已经创建过的享元对象
- 抽象享元:所有具体享元通用方法,包含对外部状态的传递
- 具体享元:继承抽象享元,包含内部状态和传递进入的外部状态
- 作用:防止类爆炸,提高性能
观察者——行为型
- 思想:将被观察者和观察者解耦
- 作用:实现一对多的关系,解耦方便扩展
- 使用场景:事件驱动……一对多的依赖关系,ue的多播委托允许多个函数绑定到一个事件上,当执行多播委托可以调用所有绑定的函数执行,ue的调用一个接口时遍历所有的接口实现
- 实现方式:
- 抽象被观察者:维护一个观察者列表, 包含注册、删除和通知的函数,并维护自身状态,当状态改变时通知所有观察者
- 具体被观察者: 继承抽象被观察者
- 抽象观察者:通常包含被通知函数,在接收到通知时执行自己的操作
- 具体观察者: 继承抽象被观察者
状态——行为型
- 思想:将每种状态和对应的行为封装到一个派生类中
- 使用场景:一个对象在在不同的状态下有不同的行为时,角色,AI,游戏,渲染 状态
- 实现方式:
- 上下文:包含当前的状态,更新状态的方法,执行行为方法
- 抽象状态:包含行为方法
- 具体状态:继承抽象状态,对应一种状态下的行为
- 作用:当添加新的状态和行为,仅需要新增派生类,无需修改ifelse,遵守开闭原则
策略——行为型
- 思想:定义了一系列算法实现相同的功能
- 使用场景:需要动态选择算法,伤害计算,抗锯齿算法
- 实现方式:
- 上下文:包含策略引用,调用具体策略的算法
- 抽象策略:算法接口
- 具体策略:具体的算法实现
- 作用:灵活的算法切换
命令——行为型
- 思想:调用者通过命令层和接收者通讯
- 使用场景:历史命令管理 (维护命令列表)
- 作用:将调用者和接收者解耦
- 实现方式:
- 调用者:调用命令对象的执行方法
- 抽象命令/请求:包含执行方法
- 具体命令:继承抽象命令,执行方法调用接收者的方法
- 接收者:接受命令并执行具体操作
中介者——行为型
- 思想:通过一个中介来负责一组对象之间的交互
- 作用:避免了对象间的相互引用
- 使用场景:对象之间存在复杂的交互关系,场景管理器
- 实现方式:
- 抽象中介:包含管理所有同事,和发送消息的方法
- 具体中介:继承抽象中介
- 抽象同事:包含对中介的引用,包含发送和接受消息的方法
- 具体同事:继承抽象同事
备忘录——行为型
- 思想:捕获对象的状态并存储
- 使用场景:存档
- 作用:支持实现撤销和恢复操作
- 实现方式:
- 发起人:创建备忘录的方法,是需要被撤销和恢复状态的对象
- 备忘录:存储发起人内部状态,只能被发起人访问
- 管理者:存储多个备忘录对象,但不了解其内部结构,通常维护撤销占和恢复栈,包含撤销和恢复方法
模板——行为型
- 思想:定义了基类,包含算法流程,将实现延迟到子类(继承体系)
- 使用场景:渲染流程,游戏循环流程,架构
- 实现方式:
- 模板:包含所有通用的方法
- 具体:继承模板类
- 作用:提高复用性
迭代器——行为型
- 思想:用迭代器遍历聚合类(自定义类)的成员,让聚合类作为vector的元素类型
- 使用场景:统一遍历不同的结构,遍历不同物品
- 实现方式:
- 抽象迭代器:通常包含检查是否有下一位,获取下一位,遍历所有元素,添加元素……操作集合的方法
- 具体迭代器:继承抽象迭代器,包含聚合类托管的对象集合的引用
- 抽象聚合类:创建迭代器的方法,为了不暴露对象的内部表示
- 具体聚合类:继承抽象聚合类,包含托管对象集合,向集合添加对象的方法, 实现创建迭代器的方法,并向其传递托管数据的引用
- 作用:提供统一遍历的接口,隐藏对象的内部表示
责任链——行为型
- 思想:请求从对象链一端进入,沿着链依次处理,直到链上的某个对象能够处理该请求为止
- 使用场景:渲染队列,碰撞检测……
- 实现方式:
- 抽象处理者:处理请求的方法,指向下一个处理者的指针
- 具体处理者:继承抽象处理者,如果能处理就执行处理,否则请求传递给下一个处理者
- 客户端:创建处理者对象链,发送请求到第一个处理者
- 作用:
- 请求的发送者和处理者解耦,发起者无需关注处理者是否处理
- 增加灵活性,方便添加处理者
解释器——行为型
- 思想:把要解释的语言写为继承体系模块,暴漏解释接口
- 使用场景:算数/正则表达式的处理,解释对话,着色器语言
- 实现方式:
- 抽象表达式:包含解释的方法
- 终结符表达式:继承抽象表达式,不能再分解的最小单元
- 非终结符表达式:继承抽象表达式,复杂表达式
- 作用:方便处理各种数据
访问者——行为型
- 思想:不改变一个维度的情况下,对另一个维度扩展
- 使用场景:一个维度不需要扩展,另一个维度需要扩展,导出为不同的格式文件
- 实现方式:
- 抽象访问者:访问不同元素的方法
- 具体访问者:继承抽象访问者
- 抽象元素:被访问的方法,通常参数为访问者引用,内部调用访问者的访问方法,并传入自身的引用
- 具体元素:继承抽象元素,被访问的目标
- 对象结构:包含所有元素,遍历元素并调用被访问的方法,参数为访问者引用,方便访问者访问
- 和装饰者区别:为现有类添加与核心功能无关的操作,而装饰者为现有类添加相关的职责
本文由作者按照 CC BY 4.0 进行授权
