文章

(1·设计模式分类、单例、观察者模式)

设计模式

  • 理解:算法是解决特定问题的模板,设计模式是优化架构的模板
  • 抽象:将通用的数据和方法提取出来的过程(房子的设计图纸)
  • 封装:将抽象出来的数据和方法封装为类/将设置成员访问权限,对外隐藏内部实现细节(一个房子)
  • 遵守高内聚,低耦合(设计原则)
    • 高内聚:模块内部数据/功能相关程度的高
    • 低耦合:模块/对象之间的依赖关系弱,修改一个模块 不会影响/不需要修改 其他模块的内部逻辑
  • 六大基本原则(设计原则)
    • 开闭原则、对象应对扩展开放,对修改关闭,在不修改现有代码的情况下添加新的功能,通常用抽象类实现
    • 单一职责原则、一个类应该只负责一项职责
    • 里氏替换原则、派生类可以扩展基类的功能,但不能改基类原有的功能
    • 接口隔离原则、接口应该被细分为更小的、更具体的接口,避免在依赖该接口时依赖其不需要的方法,
    • 依赖倒置原则、通过接口或抽象类来进行编程,而不是直接依赖于具体的实现类,抽象层和顶层不应该改变,底层可以改变
    • 迪米特法则、一个对象应当对其他对象有尽可能少的了解,只与直接类通信,不与间接类通信
  • 作用:(形成优秀的架构)
    • 易扩展(积木组合了模块,方便添加修饰,和修改)
    • 易复用(积木组合了模块,方便拷贝使用)
    • 易组合(积木零件的组合方式)
    • 简化使用(积木组合了模块,方便使用)
    • 提高安全性(积木组合了模块,并不暴漏内部实现方式,防止破坏)
  • 解耦:将原本相互依赖的模块/对象分离(把不好的结构优化)
  • 重构:在不改变现有功能情况下,改善其内部结构的过程(让房子更结实)

1743247452290

  • 分类:
    • 创建型:提供创建对象的模板
    • 结构型:提供组装成较大的结构的模板
    • 行为型:提供对象间的高效沟通和职责委派的模板

单例——创建型

  • 思想:创建整个程序中唯一 实例化(创建任意类型对象的过程)类对象(一切存储于内存的皆对象),并提供全局访问点
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 进行授权
本页总访问量