设计模式大概理解下

75 阅读14分钟

一、单例模式

1. 定义

保证类仅有一个实例,并提供该实例的全局访问点。

2.解决了什么问题

稳定点:类只有一个实例,提供全局的访问点
变化点:有多个类都是单例,能不能复用代码
解决的方法:
稳定点:抽象
变化点:扩展,①继承②组合

3.代码结构

①版本1

  • 私有的构造和析构
  • 禁掉拷贝构造,拷贝赋值,移动构造,移动赋值
  • 静态类成员函数
  • 静态私有成员变量
class Singleton{
public:
    static Singleton* GetInstance(){//使用静态函数来实现全局的访问点
        if(_instance==NULL){
            _instance = new Singleton();
        }
        return _instance;
    }
private:
    //因为只有一个单例,不希望别人去构造和析构它,把构造和析构函数放private里
    Singleton(){};//构造
    ~Singleton(){};
    Singleton(const Singleton &)=delete;//拷贝构造
    Singleton& operator=(const Singleton&)=delete;//拷贝赋值构造
    Singleton(Singleton&&)=delete;//移动构造
    //移动构造,会优化返回值:-fno-elide-constructors用来关闭这个行为
    //关闭后会先看有没有移动构造,再看有没有拷贝构造
    Singleton& operator=(Singleton&&)=delete;//移动赋值构造
    static Singleton* _instance;
};
Singleton* Singleton::_instance=nullptr;//静态化变量初始化

  • 移动构造函数:用于将资源从一个对象移动到另一个对象,避免不必要的拷贝。
  • 返回值优化(RVO/NRVO) :编译器优化技术,避免不必要的拷贝或移动操作。
  • -fno-elide-constructors:关闭返回值优化,强制编译器调用移动构造函数。
    但是版本一有缺点
    内存泄漏,这里的_instance 是通过 new 创建的,但没有对应的 delete 操作,导致内存泄漏。所以产生了版本二

②版本2

使用cstdlib头文件中的atexit()[当程序退出的时候会调用它]。

为什么静态成员函数当中可以访问该类的私有成员?
它们是友元关系。

#include<cstdlib>
using namespace std;
class Singleton{
public:
    static Singleton* GetInstance(){//使用静态函数来实现全局的访问点
        if(_instance==NULL){
            _instance = new Singleton();
            atexit(Destructor);//当程序退出的时候
        }
        return _instance;
    }
private:
    static void Destructor(){
        if(_instance!= nullptr){
            delete _instance;
            _instance=nullptr;
        }
    }
private:
    //因为只有一个单例,不希望别人去构造和析构它,把构造和析构函数放private里
    Singleton(){};//构造
    ~Singleton(){};
    Singleton(const Singleton &)=delete;//拷贝构造
    Singleton& operator=(const Singleton&)=delete;//拷贝赋值构造
    Singleton(Singleton&&)=delete;//移动构造
    //移动构造,会优化返回值:-fno-elide-constructors用来关闭这个行为
    //关闭后会先看有没有移动构造,再看有没有拷贝构造
    Singleton& operator=(Singleton&&)=delete;//移动赋值构造
    static Singleton* _instance;
};
Singleton* Singleton::_instance=nullptr;//静态化变量初始化
//还可以使用内部类和智能指针解决

但是版本2是一个单线程的,不支持多线程的。

③版本3

所以给它加锁。

#include<mutex>
using namespace std;
class Singleton{
public:
    static Singleton* GetInstance(){//使用静态函数来实现全局的访问点
       //RAII类的声明周期进行资料管理  
       //std::lock_guard<std::mutex>lock(_mutex);//3.1切换线程
        if(_instance==NULL){// 第一次检查

            std::lock_guard<std::mutex>lock(_mutex);//3.2加锁
            if(_instance== nullptr){//第二次检查
                _instance = new Singleton();
                //new 操作符的行为:
                //1.分配内存
                //2.调用构造函数
                //3.返回指针
                //多线程环境下 cpu  reorder操作
                atexit(Destructor);
            }
        }
        return _instance;
    }
private:
    static void Destructor(){
        if(_instance!= nullptr){
            delete _instance;
            _instance=nullptr;
        }
    }
private:
    //因为只有一个单例,不希望别人去构造和析构它,把构造和析构函数放private里
    Singleton(){};//构造
    ~Singleton(){};
    Singleton(const Singleton &)=delete;//拷贝构造
    Singleton& operator=(const Singleton&)=delete;//拷贝赋值构造
    Singleton(Singleton&&)=delete;//移动构造
    //移动构造,会优化返回值:-fno-elide-constructors用来关闭这个行为
    //关闭后会先看有没有移动构造,再看有没有拷贝构造
    Singleton& operator=(Singleton&&)=delete;//移动赋值构造
    static Singleton* _instance;
    static std::mutex _mutex;
};
Singleton* Singleton::_instance=nullptr;//静态化变量初始化
std::mutex Singleton::_mutex;//互斥锁初始化

 线程安全问题

  • 问题:GetInstance() 函数在多线程环境下可能会导致多个线程同时创建 Singleton 实例,从而破坏单例的唯一性。

  • 原因:if (_instance == NULL) 这一行代码没有加锁,多个线程可能同时进入这个条件判断,导致多次调用 new Singleton()

  • 解决方法:

    • 使用 双重检查锁定(Double-Checked Locking)  来确保线程安全。
    • 或者使用 局部静态变量(C++11 及以上版本)来实现线程安全的单例。

之所以用了3.1和3.2 。是因为3.1是用来读的锁,3.2是写的锁,实际上读不需要加锁,所以可以不使用,只给3.2加锁。

版本4

在c++98里是单线程的。它执行顺序是有序的。但是c++11是多线程,它会乱序。比如new 操作符的本来行为是1.分配内存2.调用构造函数(构造数据)3.返回指针。就可能变成132的顺序。那么为了解决这个问题,就可能用到内存栅栏(内存屏障)或者原子变量的同步原语。影响了执行序问题和可见性问题。

#include <atomic>
#include <mutex>
#include <iostream>
#include <cstdlib>

class Singleton {
public:
    static Singleton* GetInstance() {
        Singleton* tmp = _instance.load(std::memory_order_acquire);  // 使用 acquire 加载
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex);
            tmp = _instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);  // 释放内存屏障
                _instance.store(tmp, std::memory_order_relaxed);
                atexit(Destructor);
            }
        }
        return tmp;
    }

private:
    static void Destructor() {
        Singleton* tmp = _instance.exchange(nullptr);  // 将 _instance 设置为 nullptr
        if (tmp != nullptr) {
            delete tmp;
        }
    }

private:
    Singleton() {}  // 私有构造函数
    ~Singleton() {} // 私有析构函数

    // 删除拷贝构造函数和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 删除移动构造函数和移动赋值运算符
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

    static std::atomic<Singleton*> _instance;
    static std::mutex _mutex;
};

std::atomic<Singleton*> Singleton::_instance{nullptr};  // 静态变量初始化
std::mutex Singleton::_mutex;  // 互斥锁初始化

int main() {
    Singleton* instance = Singleton::GetInstance();
    std::cout << "Singleton instance address: " << instance << std::endl;
    return 0;
}
//原子变量:解决原子性 可见性问题
//内存屏障:解决执行序问题

image.png

原子变量
一、它解决了三个问题
原子执行的问题
可见性的问题
①原子提供了load和store来解决。
②load:可以看见其他线程最新操作的数据。
③store:通常是写操作,修改了数据让其他线程可见。
执行序的问题

内存栅栏
保证上面或者下面的语句不能下去或上来。

内存顺序模型

image.png

版本5

简化版本4
c++11的magic static特性:如果变量在初始化的时候并发同时进入声明语句并发线程会阻塞等待初始化结束

class Singleton{
public:
    static Singleton& GetInstance(){//使用静态函数来实现全局的访问点
        static Singleton instance;//构造静态变量,在这个指令执行到下面时,都不会执行。
        
        return instance;
    }
private:
    //因为只有一个单例,不希望别人去构造和析构它,把构造和析构函数放private里
    Singleton(){};//构造
    ~Singleton(){};
    Singleton(const Singleton &)=delete;//拷贝构造
    Singleton& operator=(const Singleton&)=delete;//拷贝赋值构造
    Singleton(Singleton&&)=delete;//移动构造
    Singleton& operator=(Singleton&&)=delete;//移动赋值构造

};

继承Singleton
g++ Singleton.cpp -o single -std=c++11
该版本5的优点
1.静态局部变量特性,延迟重载
2.静态局部变量特性,系统自动回收内存,自动调用析构函数
3.静态局部变量初始化时,没有 new 操作带来的cpu指令 reorder操作
4.c++11 静态局部变量初始化时,具备线程安全;

版本6

template <class T>
class Singleton{
public:
    static Singleton& GetInstance(){//使用静态函数来实现全局的访问点
        static T instance;//构造静态变量,在这个指令执行到下面时,都不会执行。
        return instance;
    }
protected:
    Singleton(){};//构造
    virtual ~Singleton(){};
private:
    Singleton(const Singleton &)=delete;//拷贝构造
    Singleton& operator=(const Singleton&)=delete;//拷贝赋值构造
    Singleton(Singleton&&)=delete;//移动构造
    Singleton& operator=(Singleton&&)=delete;//移动赋值构造

};
class DesignPattern: public  Singleton<DesignPattern>{
    friend class Singleton<DesignPattern>;//friend 能让Singleton<T>访问到DesignPattern构造函数
private://写了friend,别人就能调用private里面东西
    DesignPattern(){}
    ~DesignPattern(){}
};

二、工厂方法

1、定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。

2、解决了什么问题?

稳定点:①创建同类对象的接口(对象创建接口)。②同类对象有一个相同的职责(功能接口)。
变化点:创建对象的扩展。

3、背景

实现一个导出数据的接口,让用户选择数据的导出方式

4、代码结构

#include <string>
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:
    virtual bool Export(const std::string &data) = 0;
    virtual ~IExport(){}
};

class ExportXml : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportJson : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportTxt : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportCSV : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class IExportFactory {
public:
    IExportFactory() {
        _export = nullptr;
    }
    virtual ~IExportFactory() {
        if (_export) {
            delete _export;
            _export = nullptr;
        }
    }
    bool Export(const std::string &data) {
        if (_export == nullptr) {
            _export = NewExport();
        }
        return _export->Export(data);
    }
protected:
    virtual IExport * NewExport(/* ... */) = 0;
private:
    IExport* _export;
};

class ExportXmlFactory : public IExportFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportXml();
        // 可能之后有什么操作
        return temp;
    }
};
class ExportJsonFactory : public IExportFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportJson;
        // 可能之后有什么操作
        return temp;
    }
};
class ExportTxtFactory : public IExportFactory {
protected:
    IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportTxt;
        // 可能之后有什么操作
        return temp;
    }
};

class ExportCSVFactory : public IExportFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportCSV;
        // 可能之后有什么操作
        return temp;
    }
};

int main () {
    IExportFactory *factory = new ExportCSVFactory();
    factory->Export("hello world");
    return 0;
}

5、要点

解决创建过程比较复杂,希望对外隐藏这些细节的场景;

  • 比如连接池、线程池
  • 隐藏对象真实类型
  • 对象创建会有很多参数来决定如何创建
  • 创建对象有复杂的依赖关系

6、如何扩展代码

  • 实现创建对象接口
  • 实现功能接口
  • 多态调用

三、抽象工厂

1、定义

提供一个接口,让该接口负责创建一系列“相关或者相互依赖的 对象”,无需指定它们具体的类。

2、解决了什么问题?

稳定点:创建同类对象的接口(对象创建接口)。同类对象有多个相同的职责(功能接口)。 变化点:创建对象的扩展。

3、背景

实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式

4、代码结构

#include <string>
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:
    virtual bool Export(const std::string &data) = 0;
    virtual ~IExport(){}
};

class ExportXml : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportJson : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportTxt : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportCSV : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class IImport {
public:
    virtual bool Import(const std::string &data) = 0;
    virtual ~IImport(){}
};

class ImportXml : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

class ImportJson : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

class ImportTxt : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

class ImportCSV : public IImport {
public:
    virtual bool Import(const std::string &data) {
        // ....
        return true;
    }
};

class IDataApiFactory {
public:
    IDataApiFactory() {
        _export = nullptr;
        _import = nullptr;
    }
    virtual ~IDataApiFactory() {
        if (_export) {
            delete _export;
            _export = nullptr;
        }
        if (_import) {
            delete _import;
            _import = nullptr;
        }
    }
    bool Export(const std::string &data) {
        if (_export == nullptr) {
            _export = NewExport();
        }
        return _export->Export(data);
    }
    bool Import(const std::string &data) {
        if (_import == nullptr) {
            _import = NewImport();
        }
        return _import->Import(data);
    }
protected:
    virtual IExport * NewExport(/* ... */) = 0;
    virtual IImport * NewImport(/* ... */) = 0;
private:
    IExport *_export;
    IImport *_import;
};

class XmlApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportXml;
        // 可能之后有什么操作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IImport * temp = new ImportXml;
        // 可能之后有什么操作
        return temp;
    }
};

class JsonApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportJson;
        // 可能之后有什么操作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IImport * temp = new ImportJson;
        // 可能之后有什么操作
        return temp;
    }
};
class TxtApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportTxt;
        // 可能之后有什么操作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IImport * temp = new ImportTxt;
        // 可能之后有什么操作
        return temp;
    }
};

class CSVApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IExport * temp = new ExportCSV;
        // 可能之后有什么操作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它操作,或者许多参数
        IImport * temp = new ImportCSV;
        // 可能之后有什么操作
        return temp;
    }
};

// 相关性  依赖性    工作当中
int main () {
    IDataApiFactory *factory = new CSVApiFactory();
    factory->Import("hello world");
    factory->Export("hello world");
    return 0;
}   

5、工厂模式和抽象工厂的区别

-工厂模式只有一个职责,抽象工厂多个。

四、责任链

1、定义

使多个对象都有机会处理请求,从而避免请求的发送者和接者之间的耦合关系。将这些对象连成一条链,并沿着这条链递请求,直到有一个对象处理它为止。

2、解决的问题

稳定点:①处理的流程---请求按照链条传递②可打断
变化点:①处理节点的个数②处理顺序③处理条件

3、背景

请求流程,1 天内需要主程序批准,3 天内需要项目经理批准,3 天以上需要老板批准

4、代码结构

#include <memory>
#include <string>

class Context {
public:
    std::string name;
    int day;
};

// 稳定点 抽象  变化点 扩展 (多态)
//  从单个处理节点出发,我能处理,我处理,我不能处理交给下一个人处理
//  链表关系如何抽象

class HandleByMainProgram;
class HandleByProjMgr;
class HandleByBoss;
class HandleByBeauty;

class IHandler {
public:
    IHandler() : next(nullptr) {}
    virtual ~IHandler();
    void SetNextHandler(IHandler *next) { // 链表关系
        next = next;
    }
    bool Handle(const Context &ctx) {
        if (CanHandle(ctx)) {
            return HandleRequest(ctx);
        } else if (GetNextHandler()) {
            return GetNextHandler()->Handle(ctx);
        } else {
            // err
        }
        return false;
    }
protected:
    virtual bool HandleRequest(const Context &ctx) = 0;
    virtual bool CanHandle(const Context &ctx) = 0;
    IHandler * GetNextHandler() {
        return next;
    }
private:
    IHandler *next; // 组合基类指针
};


class Request {
public:
    Request() {
        IHandler * h0 = new HandleByBeauty();
        IHandler * h1 = new HandleByMainProgram();
        IHandler * h2 = new HandleByProjMgr();
        IHandler * h3 = new HandleByBoss();
        h0->SetNextHandler(h1);
        h1->SetNextHandler(h2);
        h2->SetNextHandler(h3);
        handler = h0;
    }
    bool Handle(const Context &ctx) {
        return handler->Handle(ctx);
    }
private:
    IHandler *handler;
};

class HandleByMainProgram : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 1)
            return true;
        return false;
    }
};

class HandleByProjMgr : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 20)
            return true;
        return false;
    }
};
class HandleByBoss : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day < 30)
            return true;
        return false;
    }
};

class HandleByBeauty : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 3)
            return true;
        return false;
    }
};

int main() {
// 抽象工厂
// nginx http 处理 
    // 设置下一指针 
    Context ctx;
    auto req = std::make_unique<Request>();
    req->Handle(ctx);
    
    return 0;
}

五、装饰器

1、定义

动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活。(一个功能上继续扩展功能)

2、解决什么问题

稳定点:①增加职责②顺序无关
变化点:一直增加

#include <iostream>
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
using namespace std;
class Context {
public:
    bool isMgr;
    // User user;
    // double groupsale;
};


class CalcBonus {    
public:
    CalcBonus(CalcBonus * c = nullptr) : cc(c) {}
    virtual double Calc(Context &ctx) {
        return 0.0; // 基本工资
    }
    virtual ~CalcBonus() {}

protected:
    CalcBonus* cc;
};

class CalcMonthBonus : public CalcBonus {
public:
    CalcMonthBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double mbonus /*= 计算流程忽略*/; 
        return mbonus + cc->Calc(ctx);
    }
};

class CalcSumBonus : public CalcBonus {
public:
    CalcSumBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double sbonus /*= 计算流程忽略*/; 
        return sbonus + cc->Calc(ctx);
    }
};

class CalcGroupBonus : public CalcBonus {
public:
    CalcGroupBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

class CalcCycleBonus : public CalcBonus {
public:
    CalcCycleBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

int main() {
    // 1. 普通员工
    Context ctx1;
    CalcBonus *base = new CalcBonus();
    CalcBonus *cb1 = new CalcMonthBonus(base);
    CalcBonus *cb2 = new CalcSumBonus(cb1);


    cb2->Calc(ctx1);
    // 2. 部门经理
    Context ctx2;
    CalcBonus *cb3 = new CalcGroupBonus(cb1);
    cb3->Calc(ctx2);
}

六、组合模式

1、定义

将对象组合成树型结构以表示“部分-整体”的层次结构。组合模 式使得用户对单个对象和组合对象的使用具有一致性。

2、解决的问题

稳定点:①树形结构的层次关系。②对单个对象和组合对象的一致性操作。
变化点:①树形结构中节点的类型(叶子节点和组合节点)。②节点的操作行为。

3、代码结构

class IComponent
{
public:
    IComponent(/* args */);
    ~IComponent();
    virtual void Execute() = 0;
    virtual void AddChild(IComponent *ele) {}
    virtual void RemoveChild(IComponent *ele) {}
};

class Leaf : public IComponent{
public:
    virtual void Execute() {
        cout << "leaf exxcute" << endl;
    }
};

class Composite : public IComponent
{
private:
    std::list<IComponent*> _list;
public:
    virtual void AddChild(IComponent *ele) {
        // ...
    }
    virtual void RemoveChild(IComponent *ele) {
        // ...
    }
    virtual void Execute() {
        for (auto iter = _list.begin(); iter != _list.end(); iter++) {
            iter->Execute();
        }
    }
};