9. 设计模式

112 阅读6分钟

单例模式

  • 一个类不管创建对象多少次,都只能得到该类型一个对象的实例
  • 原因:日志类、数据库访问等,每次调用函数直接用,而不需要创建对象增大开销

实现

  1. 构造函数私有化
  2. 提供唯一实例对象
  3. 提供唯一对象获取方式(一般返回指针)

1. 饿汉式(直接产生)

  1. 注意类的静态成员数据需要在类外初始化,静态数据成员在类中只是声明,需要在类外初始化分配内存

  2. 类中的数据成员都只是声明,如int a =10;只是在声明默认值为10,真正初始化分配内存要等到调用构造函数

  3. 特殊:

  • cpp17:inline+static可以实现类内部直接初始化,而不需要再在外部初始化,使得含静态数据成员类能写在头文件中,inline static int count = 0; // C++17 直接初始化
  • const+static整型或枚举类型:也能直接在类内部初始化,const static int MAX = 100; // 合法(C++98 起支持),c++17开始这两者组合默认含有inline,所以其他今天类型也可以在类中初始化
class Singleton {
public:
    static Singleton* getSingleton() {
        return &singleton;
    }
    void print() {
        std::cout << "Singleton instance" << std::endl;
    }
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    static Singleton singleton;
};
Singleton Singleton::singleton;

2. 懒汉式(首次调用才产生)

注意多线程问题 局部静态变量只会初始化一次,且初始化时是线程安全的

class Singleton {
public:
    static Singleton* getSingleton() {
        // 静态局部变量初始化是线程安全的,汇编代码会自动加锁
        // 静态局部变量只会初始化一次,如果已经初始化,则会直接跳过
        static Singleton singleton;
        return &singleton;
    }
    void print() {
        std::cout << "Singleton instance" << std::endl;
    }
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

工厂模式

  • 封装对象的创建过程
  • 将对象创建与使用解耦合
  • 比如在使用数据库时,用户代码只管获得数据库操作对象,然后进行增删改查,如果底层换了数据库,只需要增加对应的对象,然后去工厂函数中修改返回的对象即可,用户代码不需要改变
  • 开闭原则:对新功能
    • 对拓展开放:新增类和方法
    • 对修改关闭:不能直接修改现有的类和方法中的代码

1. 简单工厂

  1. 本质

一个工厂类封装对象的创建逻辑,客户端只需传入参数,无需知道具体实现类

  1. 缺陷

没有做到“开闭原则”,由于使用if-else,增加功能要对现有方法进行更改即添加else if

// 简单工厂类 
class ShapeFactory { 
public: 
    static std::unique_ptr<Shape> createShape(const std::string& type) { 
        if (type == "Circle") { 
            return std::make_unique<Circle>(); 
        } 
        else if (type == "Rectangle") { 
            return std::make_unique<Rectangle>(); 
        } 
        return nullptr; 
    } 
};

// 客户端代码
auto circle = ShapeFactory::createShape("Circle");

2. 工厂方法

  1. 本质
  • 借助继承与多态来实现不同对象的实例化
  • 首先创建工厂抽象类(用于客户代码指向具体工厂实现多态),然后为每个对象实现一个具体的工厂类继承自工厂抽象类
  • 需要实例化对象时,调用对应的具体工厂类即可
  1. 优缺点
  • 实现了开闭原则,新增产品只需要添加新工厂类即可
  • 工厂类会很多,具体工厂与类是一一对应
  • 很多产品是有关联的
// 抽象工厂类
class Application {
public:
    virtual std::unique_ptr<Document> createDocument() = 0;
    void newDocument() {
        auto doc = createDocument();
        doc->save();
    }
    
    virtual ~Application() = default;
};

// 具体工厂类
class TextApplication : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<TextDocument>();
    }
};

class SpreadsheetApplication : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<SpreadsheetDocument>();
    }
};

// 客户端代码
int main() {
    std::unique_ptr<Application> app = std::make_unique<TextApplication>();
    app->newDocument();
    
    app = std::make_unique<SpreadsheetApplication>();
    app->newDocument();
    
    return 0;
}

3. 抽象工厂

  1. 本质
  • 与工厂区别在于,是对一组关联关系的产品簇提供产品对象的统一创建
  • 工厂与类是一对多的
  1. 优缺点
  • 可以确保创建的对象是兼容的,适合产品族场景
  • 扩展新产品簇困难,需要修改抽象工厂接口
// 抽象工厂
class GUIFactory {
public:
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
    virtual ~GUIFactory() = default;
};

// 具体工厂1
class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    
    std::unique_ptr<Checkbox> createCheckbox() override {
        return std::make_unique<WindowsCheckbox>();
    }
};

// 具体工厂2
class MacOSFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacOSButton>();
    }
    
    std::unique_ptr<Checkbox> createCheckbox() override {
        return std::make_unique<MacOSCheckbox>();
    }
};

// 上面工厂能对多个类实例化

代理模式

通过代理类,来控制实际对象的访问权限,比如电影分为免费、vip和用券购买

  1. 实现
  • 存在抽象类
  • 代理类替代委托类,两者为兄弟关系(继承同一个抽象类),每个代理代表一种身份,比如普通用户和vip用户会有两个对应的代理类
  • 代理类中存在委托类实例(组合关系),也实现了委托类的方法,内部进行权限控制后调用委托类的方法
  • 客户直接访问的是代理对象

image.png

装饰器模式

不改变现有对象的结构下强化类的功能,和代理模式基本一致,区别在于用途,代理一般是一对一,而装饰器可能因为要强化功能而多层嵌套

  1. 实现(和代理相同)
  1. 定义一个公共接口
  2. 实现具体组件类
  3. 创建包装类(装饰器/代理)持有组件引用
  4. 在包装类中添加额外逻辑
  1. 与代理模式区别 装饰器模式让蛋糕更美味,代理模式决定谁能吃这个蛋糕

适配器模式

让不兼容的接口可以在一起工作(转换头)

  1. 实现
  • 适配器是一个中间对象,继承自老接口
  • 适配器存在新接口的引用
  • 使用新接口的方法来实现老接口相关方法
  • 用户代码:1.适配器接受新接口,2.用户代码原本的方法接受适配器

观察者-监听模式(发布-订阅模式)

多个对象依赖一个对象时(如曲线图、柱状图依赖数据对象),依赖的对象发生改变后其他对象能收到相应的通知,建立对象间一对多的依赖关系

  1. 实现
  • 主题内有依赖他的观察者的引用
  • 主题更新,则调用观察者的相关方法