设计模式

策略模式

策略模式定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立与使用算法的客户。

主要组成部分:

  • 策略接口,定义算法的接口,通常是一个具有执行方法的接口
  • 具体策略,实现策略接口的具体算法
  • 上下文,持有一个策略类的引用,实际调用算法接口的方法

优点:

  • 避免开闭原则,可以在不修改现有代码的情况下引入新的策略
  • 避免使用多个条件判断,通过策略类来替代条件判断语句
  • 提高代码的可维护性和可读性

缺点:

  • 增加对象数目
  • 客户端必须知晓所有策略

观察者模式

观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更行。

主要组成部分:

  • 主题(被观察对象),是指被观察的对象。它提供了注册、移除和通知观察者的方法
  • 观察者,定义了一个更新接口,用于接受主题的通知
  • 具体主题,实现了主题接口,维护了一个观察者列表,当它的状态发生改变时,通知所有注册的观察者
  • 具体观察者,实现了观察者接口,以便接受主题的通知并进行相应的更新

优点:

  • 解耦,观察者模式将观察者与被观察者解耦,使他们可以独立变化
  • 灵活性,可以在运行时增加或者删除观察者
  • 支持广播语言,被观察者会自动通知所有的注册观察者,而无需知道观察者的具体信息

缺点:

  • 可能引起性能问题
  • 可能导致复杂循环

装饰者模式

装饰者模式动态地把责任附加在对象上,若要扩展功能,装饰者比继承更有弹性的替代方案。

主要组成部分:

  • 组件,定义一个对象接口,可以给这些对象动态地添加职责
  • 具体组件,实现组件接口的基本对象
  • 装饰器,持有一个组件对象的引用,并且实现组件接口。装饰器基类的主要作用是定义接口并持有一个组件对象
  • 具体装饰器,实现装饰器类,负责向组件添加职责

优点:

  • 遵循开闭原则,可以在不修改现有代码的情况下为对象添加新的功能
  • 灵活性高,可以通过多次使用装饰器实现复杂的功能组合
  • 职责分离,每个装饰器只负责增加一部分功能,符合单一职责原则

工厂模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确制定具体的类

关于简单工厂

  1. 简单工厂并不算设计模式
  2. 简单工厂把主方法(客户)中的具体对象创建过程分离,好处不仅是后期扩展时修改主体更改了,还有从多个客户(其他方法)中复用同一个工厂,更加OOP
  3. 常用的写法:一种写法是使用类,另一种是直接用静态方法(但无法继承改变行为)

关于工厂方法

  1. 简单工厂只处理一个类(强调一对一),而工厂方法在主类(抽象的Creator类)中声明抽象的创建方法,让创建的执行延迟到子类的实现中
  2. 工厂方法的创建过程依赖于抽象的产品类(依赖倒置)
  3. 工厂方法中创建者类和产品类都是抽象的(这样的做法是哪怕只有一个具体创建者,也能实现更优的解耦)

简单的说简单工厂就是一手包办的工厂,而工厂方法更是一个大的框架

工厂方法是抽象的,依赖子类来处理对象的创建,这样超类的代码与子类的创建就能实现解耦(你需要子类创建的对象具有一定的规范,这时候在抽象的超类中有具体的执行方法和抽象的工厂方法,这就实现了子类的区分与相应的约束,也就是说各个子类决定创建的对象有所不同,但执行方法都在超类的掌控之下)

关于抽象工厂

  1. Factory定义为接口,用于处理Pizza的原料(内部成员)
  2. 抽象的Pizza类中对原料的处理方法(preapre)也是抽象的
  3. 具体的Pizza实例中有处理原料的工厂的引用,用于具体prepare过程中(需要的原料必须来自工厂)
  4. 从3可看出与工厂方法相比,抽象工厂对Pizza的约束更大(原料的产生只能来自工厂),此时一个Pizza的诞生是 Pizza pizza = new XjbPizza(XjbIngredientFactory)
  5. 抽象工厂时产品家族和具体工厂实现解耦(抽象工厂的做法是组合,而工厂方法是继承)

优点:

  • 解耦,将对象的创建和使用分离,提高系统的灵活性和可维护性
  • 遵循开闭原则,可以通过增加新的具体工厂和产品来扩展系统,而不影响现有代码
  • 高内聚,将创建逻辑集中在一个地方,边缘管理

单例模式

单例模式确保一个类只有一个实例,并提供全局访问点

单例模式比较简单,不过应用广泛,比如线程池、缓存、各种硬件驱动都使用了单例模式

单例模式与静态的全局变量相比,更为节省资源,对一些对资源敏感的对象而言更是如此,因为单例模式只在使用到的时候才会创建对象(延迟实例化)

如何实现:构造函数私有化,创建一个静态的方法调用该构造函数获取实例,并加以判断(再次调用,如果private static的单例已经创建非null,那就直接返回),这样在单线程下是没有问题的

线程安全的懒汉式:需要双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <mutex>

class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;

Singleton() {}

public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

static Singleton* getInstance() {
// 第一次检查是否存在实例
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
// 第二次检查是否存在实例
// 两次检查都没有才会创建新的实例
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
  1. 唯一实例,确保系统中只有一个实例,节省资源。
  2. 全局访问点,提供全局访问点,方便访问。
  3. 延迟实例化,可以延迟实例化,直到使用时才创建(懒汉式)。

命令模式

命令模式将请求封装成对象,这可以让你使用不同的请求、队列,或者日志请求来参数化其他对象

命令模式也可以支持撤销操作

适配器模式与外观模式

适配器模式将一个类的接口,转换成客期望的另一个接口

适配器让原本接口不兼容的类可以合作无间

外观模式提供一个统一的接口,用来访问子系统中的一群接口

外观定义了一个高层接口,让子系统更容易使用

简单的来说适配器模式就是用来解决兼容性问题的方案

参考:Head First 设计模式学习笔记