1.2.2结构型设计模式

单例模式

定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ------《设计模式》GoF

稳定点:类只有一个实例,提供全局的访问点。---- 抽象

变化点:有多个类都是单例,能不能复用代码。----扩展:继承;组合。

需要有私有的构造和析构;禁掉拷贝构造、拷贝构造、移动构造、移动赋值;有静态成员函数;有静态私有成员变量,通过它在堆上分配内存;

版本一

cpp 复制代码
// c++智能指针:unique_ptr可以解决堆上内存不释放的问题
class Singleton { 
public:     
    static Singleton * GetInstance() {         
        if (_instance == nullptr) {            
             _instance = new Singleton();//此时堆上分配的内存不会被释放,导致内存泄漏       
        }        
        return _instance;    
    } 
private:     
    Singleton(){}; //构造     
    ~Singleton(){};     
    Singleton(const Singleton &) = delete; //拷⻉ 构造     
    Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造     
    Singleton(Singleton &&) = delete;//移动构造     
    Singleton& operator=(Singleton &&) = delete;//移动拷贝构造     
    static Singleton * _instance;  // 静态全局区分配内存,指针指向堆上的内存块
};
Singleton* Singleton::_instance = nullptr;//静态成 员需要初始化 

版本二

为什么静态成员函数当中可以访问该类对象的私有成员?同一个类的对象都可以访问私有成员;

静态成员函数在 C++ 中与普通成员函数有所不同。它的一个关键特性是,静态成员函数不依赖于类的实例(即对象),而是属于类本身。因此,静态成员函数没有 this 指针,不能访问类的非静态成员(如实例的成员变量和成员函数)。但是,静态成员函数可以访问类的私有成员,这是因为静态成员函数依然是类的一部分,编译器会认为静态成员函数是该类的"一部分"。即使静态成员函数没有对象实例,类的私有成员(包括私有变量和私有成员函数)对类的静态成员函数是可见的。

解决了内存泄漏,可以调用析构函数进行delete

cpp 复制代码
#include<cstdlib>
class Singleton { 
public:     
    static Singleton * GetInstance() {         
        if (_instance == nullptr) {             
            _instance = new Singleton();  // 会产生资源竞争           
            atexit(Destructor);// 调用该函数,解决了内存泄露问题        
        }         
        return _instance;    
    } 
private:     
    static void Destructor() {         
        if (nullptr != _instance) { // 此时主动释放内存,指针置空             
            delete _instance;             
            _instance = nullptr;        
        }
    }     
    Singleton(){}; //构造     
    ~Singleton(){};     
    Singleton(const Singleton &) = delete; //拷⻉构造     
    Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造     
    Singleton(Singleton &&) = delete;//移动构造     
    Singleton& operator=(Singleton &&) = delete;//移动拷贝构造     
    static Singleton * _instance; 
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
// 还可以使⽤内部类,智能指针来解决; 此时还有线程安全问题 

版本三

加入了多线程,且引入了双重检测,主要用来解决读和写,因为只有第一次 访问才会调用new,第一个 if (_instance == nullptr)判断是专为读来准备的,只有第一次访问的时候才考虑加锁。

cpp 复制代码
#include <mutex> 
class Singleton { // 懒汉模式 lazy load 
public:     
    static Singleton * GetInstance() {       
        // std::lock_guard lock(_mutex); // 3.1 切换线程   此时会浪费锁 :只有第一次访问全局检测点才涉及到写操作     
        if (_instance == nullptr) {             
            std::lock_guard lock(_mutex); // 3.2             
            if (_instance == nullptr) {  // 双重检测double check               
                _instance = new Singleton(); //会产生资源竞争,所以加锁 
                // operator neww操作符               
                // 1. 分配内存                 
                // 2. 调用构造函数                 
                // 3. 返回指针                 
                // 多线程环境下 cpu reorder操作                 
                atexit(Destructor);            
            }        
        }         
        return _instance;    
    } 
private:     
    static void Destructor() {         
        if (nullptr != _instance) {             
            delete _instance;             
            _instance = nullptr;        
        }    
    }     
    Singleton(){}; //构造    
    ~Singleton(){};     
    Singleton(const Singleton &) = delete; //拷⻉构造 
    Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造     
    ]Singleton(Singleton &&) = delete;//移动构造     
    Singleton& operator=(Singleton &&) = delete;//移动拷贝构造     
    Singleton * _instance;     
    static std::mutex _mutex; 
}; 
Singleton* Singleton::_instance = nullptr;//静态成 员需要初始化 
std::mutex Singleton::_mutex; //互斥锁初始化

版本四

版本三是单线程,版本四是应对多核时代,考虑了指令重排。同步原语来解决,包括原子变量和内存栅栏

原子变量:原子执行的问题;可见性问题;执行序问题;

load:可以看见其他线程最新操作的数据

store:修改数据让其他线程可见

内存栅栏:可见性问题;执行序问题

cpp 复制代码
// volitile 
#include <mutex  
#include <atomic> 
class Singleton { 
public:  
     /* static Singleton * GetInstance() {  
          // 原子变量解决 原子性 可见性 执行序      
        Singleton* tmp = _instance.load(std::memory_order_acquire);
        if (tmp == nullptr) {             
            std::lock_guard<std::mutex> lock(_mutex);             
            tmp = _instance.load(std::memory_order_acquire);
            if (tmp == nullptr) {                 
                tmp = new Singleton;
                _instance.store(tmp, std::memory_order_release);
                atexit(Destructor);            
            }       
        }         
        return tmp;    
    } */
     //同步变量
    // 原子变量解决 原子性 可见性
    // 内存栅栏解决 执行序 可见性
    static Singleton * GetInstance() {         
        Singleton* tmp = _instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acqui re);//获取内存屏障 
        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_relea se);//释放内存屏障 
                _instance.store(tmp, std::memory_order_relaxed);
                atexit(Destructor);            
            }       
        }         
        return tmp;    
    } 
private:     
static void Destructor() {         
    Singleton* tmp = _instance.load(std::memory_order_relaxed);         
    if (nullptr != tmp) {             
        delete tmp;        
    }    
}     
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;//静 态成员需要初始化 
std::mutex Singleton::_mutex; //互斥锁初始化 
// g++ Singleton.cpp -o singleton -std=c++11

版本五

版本四的简化版

cpp 复制代码
// c++11 magic static 特性:如果当变量在初始化的时候, 并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。
// c++ effective 

class Singleton 
{ 
public:     
    static Singleton& GetInstance() {         
        static Singleton instance;         
        return instance;    
    } 
private:     
    Singleton(){}; //构造     
    ~Singleton(){};     
    Singleton(const Singleton &) = delete; //拷⻉构造     
    Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造     
    Singleton(Singleton &&) = delete;//移动构造     
    Singleton& operator=(Singleton &&) = delete;//移动拷贝构造 
}; 
// 继承 Singleton 
// g++ Singleton.cpp -o singleton -std=c++11 
/*该版本具备 版本5 所有优点: 
1. 利⽤静态局部变量特性,延迟加载; 
2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数; 
3. 静态局部变量初始化时,没有 new 操作带来的cpu指令 reorder操作; 
4. c++11 静态局部变量初始化时,具备线程安全; 
*/

版本六

在版本五基础上加了一个多态,解决了变化点问题

cpp 复制代码
template<typename T> 
class Singleton { 
public: 
    static T& GetInstance() {         
        static T instance; // 这⾥要初始化 DesignPattern,需要调⽤DesignPattern 构造函数,同时会调⽤⽗类的构造函数。         
        return instance;    
    } 
protected:   // 让子类能够得以构造  
    virtual ~Singleton() {}     
    Singleton() {} // protected修饰构造函数,才能让别⼈继承 
private:     
    Singleton(const Singleton &) = delete; //拷⻉ 构造     
    Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造     
    Singleton(Singleton &&) = delete;//移动构造     
    Singleton& operator=(Singleton &&) = delete;//移动拷贝构造 
}; 
class DesignPattern : public Singleton {     
    friend class Singleton<DesignPattern>; 
private:     
    DesignPattern() {}     
    ~DesignPattern() {} 
}; 

工厂方法

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟到子类。 ------《设计 模式》GoF

为什么要有工厂模式而不直接使用new?除了new还有很复杂的构造流程。

解决了什么问题?

稳定点:创建同类对象的接口,对象创建接口;同类对象有一个相同的职责,功能接口。

变化点:创建对象的扩展

设计原则:最小知道原则;面向接口编程;

背景

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

要点

解决创建过程比较复杂,希望对外隐藏这些细节的场景; 比如连接池、线程池隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系;

本质

延迟到子类来选择实现;

抽象工厂

定义

提供一个接口,让该接口负责创建一系列"相关或者相互依赖的 对象",无需指定它们具体的类。 ------《设计模式》GoF

解决了什么问题?

稳定点:创建同类对象的接口,对象创建接口;同类对象有多个相同的职责,功能接口。

变化点:创建对象的扩展

工厂方法与抽象工厂的区别:抽象工厂需要创建一系列功能对象(多个功能接口);工厂方法创建一类功能的对象。

背景

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

结构图

责任链

定义

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

解决的问题:

稳定点:处理流程:请求按照链条传递:链表关系,接口;可打断

变化点:处理节点的个数;处理顺序;处理节点。

要点

  • 解耦请求方和处理方,请求方不知道请求是如何被处理,处理方 的组成是由相互独立的子处理构成,子处理流程通过链表的方式 连接,子处理请求可以按任意顺序组合;

  • 责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;

  • 责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;

  • 将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;

本质

分离职责,动态组合;

结构图

参考链接:0voice · GitHub

相关推荐
杨充31 分钟前
10.可测试性实战设计
设计模式·开源·代码规范
杨充39 分钟前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 小时前
2.面向对象的特性
设计模式
杨充1 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯4 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯13 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式