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

相关推荐
__万波__3 小时前
二十三种设计模式(八)--装饰器模式
java·设计模式·装饰器模式
繁华似锦respect14 小时前
C++ 智能指针底层实现深度解析
linux·开发语言·c++·设计模式·代理模式
繁华似锦respect1 天前
单例模式出现多个单例怎么确定初始化顺序?
java·开发语言·c++·单例模式·设计模式·哈希算法·散列表
繁华似锦respect1 天前
C++ 内存分配器-allocator
开发语言·c++·设计模式
Zaralike1 天前
Java设计模式
java·开发语言·设计模式
开心香辣派小星1 天前
23种设计模式-14迭代器模式
设计模式·迭代器模式
2301_803554521 天前
Pimpl(Pointer to Implementation)设计模式详解
c++·算法·设计模式
__万波__1 天前
二十三种设计模式(七)--桥接模式
设计模式·桥接模式
老朱佩琪!1 天前
在Unity中实现状态机设计模式
开发语言·unity·设计模式