单例设计模式(仅介绍懒汉式单例)
何为单例设计模式?
官方定义: 保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ——《设计模式》GoF
口语表达:简单来说就是这个类只可以实例化出一个对象, 谓之单例.
如何可以达成一个类只能实例化出来一个对象的要求, 限制, 限制创建(构造), 根据一个类仅仅实例化一个对象的这个要求不难想到这个对象需要和类同生命周期, 所以这个唯一的对象需要是一个static对象, static 方法获取这个对象
(因为无法通过obj.GetInstance的方式获取,所以只能申明为static静态)
基础懒汉单例设计模式
class Singleton { public: static Singleton* GetInstance() { if (nullptr == _instance) { _instance = new Singleton; } return _instance;//返回单例对象 } private: Singleton() {}//私有化构造函数 Singleton& operator= (const Singleton& obj){}//禁止掉赋值 Singleton(const Singleton& obj) {}//禁止掉拷贝构造 static Singleton* _instance;//单例对象 }; Singleton* Singleton::_instance = nullptr;
上述这款懒汉式的单例设计模式, 问题分析:
- 对于_instance单例对象的回收问题, 我们new 出来的Singleton 对象何时释放的问题, static对象的回收不会自动调用delete,不会负责堆区内存的回收
- 针对多线程重入的问题, 多线程同时new多次的问题.
内存回收懒汉单例设计模式
针对内存回收问题的三种解决方案
- 智能指针
- 内部垃圾回收类回收
- 使用atexit函数做回收处理
内部类实现内存回收
//内部类进行垃圾回收 class Singleton { public: static Singleton* GetInstance() { if (nullptr == _instance) { _instance = new Singleton; } return _instance;//返回单例对象 } private: class Garbage { public: ~Garbage() { if (_instance) { delete _instance; cout << "回收_instance对象" << endl; _instance = nullptr; } } }; Singleton() {}//私有化构造函数 Singleton& operator= (const Singleton& obj){}//禁止掉赋值 Singleton(const Singleton& obj) {}//禁止掉拷贝构造 static Singleton* _instance;//单例对象 static Garbage garbage; }; Singleton* Singleton::_instance = nullptr; Singleton::Garbage Singleton::garbage;
注册atexit函数处理
class Singleton { public: static Singleton* GetInstance() { if (nullptr == _instance) { _instance = new Singleton; atexit(Destrustor); } return _instance;//返回单例对象 } private: static void Destrustor() { if (_instance) { delete _instance; cout << "析构_instance" << endl; _instance = nullptr; } } Singleton() {}//私有化构造函数 Singleton& operator= (const Singleton& obj) {}//禁止掉赋值 Singleton(const Singleton& obj) {}//禁止掉拷贝构造 static Singleton* _instance;//单例对象 }; Singleton* Singleton::_instance = nullptr;
加锁懒汉单例设计模式(双检查加锁提升效率)
//如何进行双检测加锁 class Singleton { public: static Singleton* GetInstance() { if (nullptr == _instance) { //进来之后说明是还没有创建单例对象,加锁, unique_lock<mutex> lck(mtx); if (nullptr == _instance) {//加锁保证进来的仅仅只有一个线程. _instance = new Singleton; atexit(Destrustor); } } return _instance;//返回单例对象 } private: static void Destrustor() { if (_instance) { delete _instance; cout << "析构_instance" << endl; _instance = nullptr; } } Singleton() {}//私有化构造函数 Singleton& operator= (const Singleton& obj) {}//禁止掉赋值 Singleton(const Singleton& obj) {}//禁止掉拷贝构造 static Singleton* _instance;//单例对象 static mutex mtx; }; Singleton* Singleton::_instance = nullptr; mutex Singleton::mtx;
为何需要使用双重检测加锁. 保证仅仅只有一个线程new Singleton的情况下还可以减少加锁, 如果外围没有再多一层的if检测,可以吗,绝对也是OK的,但是存在很多的冗余加锁。因为仅仅只有第一次_instance == nullptr的时候是需要加锁new的,但是后面如果仅仅只是return _instance是完全没有必要枷锁的 --- 故而采取双重检测
此种方式下已经算很完美了,但是还是存在问题.
--- CPU指令重排的问题, 很可能导致_instance单例对象没有调用构造函数初始化以至于单例对象是随机值.
#include <mutex> #include <atomic> class Singleton { public: static Singleton * GetInstance() { Singleton* tmp = _instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_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.load(std::memory_order_relaxed); if (nullptr != tmp) { delete tmp; } } Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} static std::atomic<Singleton*> _instance; static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance;//静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化
巧用static对象的懒汉单例设计模式
class Singleton { public: ~Singleton() { cout << "析构对象_instance" << endl; } static Singleton& GetInstance() { static Singleton _instance; return _instance; } private: Singleton() {} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} };
如上的方式是完美解决了最初一款单例模式的所有问题的。 自动最后调用Destor回收内存,而且多线程访问也不会出现任何问题. 因为它仅仅只是第一次会init,后面都不会在创建对象. 利用了static静态对象的仅仅构造一次的优势., 看吧写三次依旧仅仅只是调用一次构造
如上版本单例模式的优势所在
- 利用静态局部变量特性,延迟加载
- 利用静态局部变量的特性, 系统自动回收内存,自动调用析构
- 静态局部变量的初始化没有new操作带来的CPU指令reorder问题
- C++静态局部变量的初始化, 自带线程安全
这种单例设计模式是否已经无敌了,完美了,看上去好像是这样的。但是还是存在这样一个问题,一旦需要继承这个单例类,应该如何书写??? 才可以使得可以通过继承的方式创建仍以具体类的单例对象
template<class T > class Singleton { public: static T& GetInstance() { static T _instance;//定义并且初始化单例对象 return _instance;//return 单例对象 } protected: //一旦继承析构函数一定需要进行虚析构 virtual ~Singleton() { cout << "析构单例对象" << endl; } Singleton() { cout << "构造单例对象" << endl; } Singleton(const Singleton&) {}//隐藏拷贝构造 Singleton& operator= (const Singleton&) {}//隐藏赋值重载 }; //继承书写, 通过如此形式实现继承单例类的书写, //如此可以实现了任意类型的单例类实例化 class DesignPattern : public Singleton<DesignPattern> { friend class Singleton<DesignPattern>; //声明为友元, 使得Singleon<DesignPattern>类可以访问private成员 private: DesignPattern() { cout << "调用DesignPattern构造" << endl; }//私有化构造函数 DesignPattern& operator=(const DesignPattern&) {}//私有化赋值重载 DesignPattern(const DesignPattern&){}//私有化拷贝构造 virtual ~DesignPattern() { cout << "调用DesignPattern析构" << endl; } };
观察者设计模式
官方定义: 定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《 设计模式》 GoF
口语解释: 说白了就是定义一个类对象跟多个其他类对象之间的一种依赖关系, 其他多个类对象依赖于这一个类对象. 一旦这一个类对象状态发生了改变. 其他依赖这个对象的所有其他类对象(观察者) 都会得到通知进行自动更新变化
通知依赖关系——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。
怎么说:这个强依赖关系, 过于的耦合了, 我们需要实现运行时绑定, 晚绑定, 依赖抽象,依赖倒置来解除这个对具体对象的强依赖. 改为对抽象的依赖.
我们还是通过前后对比书写的方式来重构研究一下具体缺乏哪些设计原则, 添加哪些设计原则重构最终实现了观察者模式
场景引入: 比如说存在这样一个场景,正常情况下,大家上班都在进行摸鱼防水,轻松, 但是大家也都是很聪明的存在, 是懂得观察的存在. 要是老板来了,大家就会立刻Update更新当前的状态,所作的事情.
--- 此时的所有Wocker 或者说Staff 全部都是监视者. 而老板就是这个被监视的对象,老板的状态改变就会引起所有监视者的状态改变
class Staff; class Boss { private: string action; public: Boss(string _action): action(_action) {} string& GetAction() { return action; } }; class Staff { private: string name; Boss boss; public: Staff(string _name, Boss _boss) : name(_name) , boss(_boss) { } void Run() { if (boss.GetAction() == "老板从大门走进来了") { cout <<"name: " << name << "老板来了,"<< "立刻马上认真工作" << endl; } else if (boss.GetAction() == "老板走出去了") { cout <<"name: " << name << "老板走了,"<< "立马开始摸鱼起来了" << endl; } } }; int main() { Staff st("张三", Boss("老板走出去了")); st.Run(); Staff st2("李四", Boss("老板从大门走进来了")); st2.Run(); return 0; }
如上代码可行不可行, 其实也可以实现功能. 老板回来了,出去了老板的状态可以影响到它的不同员工的状态.
但是上述代码用一句名人说的话就是 bad smell. 嗅到了坏味道. why?
员工类高度耦合依赖Boss老板类, 一旦老板类的状态发生任何变化都可能导致员工类无法正常使用, 另外没有遵循设计原则. 不遵循依赖倒置, 是编译时绑定依赖, 而不是运行时依赖. 毫无扩展性可言, 比如说需要通知一下其他的事情, 状态,便需要修改类. 而不是扩展类. 没有做到封装可能的变化点
改进代码如下:
class Observer { public: //Subject目标变化带来的Update virtual void Update(int) = 0; protected: virtual ~Observer() {} }; //目标,被观察者 class Subject { public: void Attach(Observer* pob) { pobs.push_back(pob); } void Detach(Observer* pob) { auto it = find(pobs.begin(), pobs.end(), pob); if (it != pobs.end()) { it = pobs.erase(it); } } void Notify() { auto it = pobs.begin(); while (it != pobs.end()) { (*it)->Update(action); ++it; } } virtual void SetAction(int action) = 0; protected: int action; vector<Observer*> pobs; virtual ~Subject(){} }; class Boss : public Subject { public: //action 0 表示老板离开, 1 表示老板进入 virtual void SetAction(int action) { this->action = action; Notify(); } }; class Staff : public Observer { public: virtual void Update(int action) { if (action == 0) { cout << "老板走了大家可以划水了" << endl; } if (action == 1) { cout << "老板进来了, 大家快好好工作" << endl; } } };
对比前后,上述设计模式采用了哪些设计原则以及有哪些特征
开闭原则, 对扩展开放, 对修改封闭
依赖倒置原则, 都不是依赖具体的类, 而是依赖抽象, 将编译时依赖变成了运行时依赖 (解决了具体类之间的强耦合性) --- 封装变化点
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
目标对象不需要知道谁订阅了自己,也就是不需要知道哪些对象在观察自己
观察者自己决定是否订阅通知,目标对象并不关注谁订阅了
在目标对象状态改变的时候通知观察者的顺序不固定, 随机
Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
观察者模式的特点总结
- 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合
- 观察者自己决定是否订阅通知,目标对象也不关系谁订阅了
观察者模式的记忆技巧
一个对象状态变化引发的联动现象 ---- 状态改变, 触发联动
策略模式
官方定义: 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。 ——《设计模式》 GoF
口语解释:封装算法流程. 各个算法之间是等效的, 抽象算法特征进行扩展, 核心关键在于使得算法可以独立于客户程序变化 (解耦合:编译时依赖转换为运行时依赖.)
有那么一点子特别像是工厂模式。只不过工厂模式是创建对象的创建模式. 而这个策略模式是创建算法,创建策略, context是提供上下文, 选取策略的类
首先先写一份拉跨的代码, 没有策略的代码
场景引入: 收取税收, 对于不同国家按照不同的税收策略算法进行收税
enum TaxType { CN_Tax, US_Tax, USA_Tax, FR_Tax }; class TaxClass { TaxType tax; public: double CalculateTax() { //... if (tax == CN_Tax) { //...一种税收算法 } else if (tax == US_Tax) { //... 另一种税收算法 } else if (tax == USA_Tax) { //... 再一种税收算法 } else if (tax == FR_Tax) { //... 再再一种税收算法 } //... } };
分析弊端:
职责众多, 不满足单一职责的设计原则
不满足开放封闭原则,类的扩展性极差, 如果CalculateTax是稳定的, 固定不变的模块, 这个使用if else是没啥问题的, 比如说 if (男) else (女) 这种固定的不会扩展的可以这样写. 但是对别国的关税税收这个完全是不确定的事情. 税收的计算算法也是一个复杂的流程, 新增税收以及更改税收算法都需要直接修改TaxClass税收类
众多职责, 复杂的算法耦合在一个Tax类中,使得TaxClass类变得复杂,对于不适用的算法的判断也是一种性能负担
耦合度过高, 算法和Tax类的耦合度过高, 如何消除众多的if条件判断,解除耦合性
class Context;//提供上下文, 用于计算 //抽象税收策略类 class TaxStrategy { public: virtual double CalculateTax(const Context& ctx) = 0; virtual ~TaxStrategy() {} }; //扩展具体的税收策略 class CNTax : public TaxStrategy { public: virtual double CalculateTax(const Context& ctx) { //... } }; class USTax : public TaxStrategy { public: virtual double CalculateTax(const Context& ctx) { //... } }; class USATax : public TaxStrategy { public: virtual double CalculateTax(cosnt Context& ctx) { //... } }; class TaxClass { private: TaxStrategy* strategy;//strategy 策略指针, 指向一个具体的策略. //利用晚绑定实现运行时依赖具体的strategy public: double Calculate() { Context ctx; return strategy->CalculateTax(ctx); } };
对比前后添加的设计原则:
开放封闭原则, 对于修改封闭了,而对扩展开放了.
单一职责原则, 很明显的将大量的算法分离除去了,而不是像之前耦合在一个类中. 使得一个具体的策略类专注于实现一个算法, 职责单一
解除了耦合性, 将算法和对象解耦合, 不再依赖于众多的if 判断选择算法
Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
最后再谈一谈什么时候使用策略设计模式吧?
不是说看见了if else if就一定需要使用策略模式. 简单固定的几个if else if判断是没有必要使用策略模式的,策略模式的使用更多的是为了便于将来的扩展 , 如果你对于需求的熟悉,明显的知道将来的扩展方向,那你当然可以将其设计为策略模式呀. 利于将来的扩展. --- 或者一开始很急,先简单的使用if else if 实现需求,之后再重构为策略模式
策略模式记忆核心一句话:分离算法,抽象策略,选择生产策略
感觉策略的生产可以采取工厂模式,感兴趣的可以拓展一下.
责任链设计模式(数据结构模式)
官方定义: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有⼀个对象处理它为止。 ——《设计模式》GoF
通俗解释: 使用一条链将所有的处理方法的对象链接起来, 一旦有请求到来, 仅仅只需要将请求交给责任链中即可, 具体是哪个对象处理的这个请求, 我不需要关心, 能不能处理完成也不关心...
----将请求对象跟具体的处理对象解耦合. 解除依赖. 请求对象不需要绑定依赖于具体的处理对象了, 像极了队列缓冲区,解耦合的思想, 异步解耦合等这种思想.
场景引入: 请假流程,1天内需要主程序批准,3天内需要项目经理批准,3天以上需要老板批准
首先是第一款实现:
//提供上下文 class Context { public: string name; int day; }; //请假请求 class LeaveRequest { public: //按照上下文处理请求 bool HandleRequest(const Context& ctx) { if (ctx.day <= 1) { HandleByMainProgram(ctx); } else if (ctx.day <= 3) { HandleByProMgr(ctx); } else { HandleByBoss(ctx); } } private: bool HandleByMainProgram(const Context& ctx) {//主程序处理 } bool HandleByProMgr(const Context& ctx) {//项目主管处理 } bool HandleByBoss(const Context& ctx) {//老板处理 } };
上述代码的问题何在? LeaveRequest类的不稳定性, 如果其中的判断增多就需要再新增处理. 耦合度高. Contex类需要知道LeaveRequest类对象是否可以处理自己的请求. 而且Contex类对象高度依赖于LeaveRequest类对象, leaveRequest也同样依赖于Contex对象。
这样的请求发送和接受者之间的高度耦合,不符合设计原则. 所以我们需要对请求的处理进行抽象, 以及通过链接数据结构的方式解除耦合性.
(如何理解使用链表,链接不同的处理,形成职责链可以解除耦合性? 有了职责链, 我请求者不需要确定知晓处理请求的对象, 仅仅只需要将自己的需求抛入到职责链即可. 同样职责链也不要知晓有哪些对象可以向自己进行请求. 我只管处理请求)
//提供上下文 class Context { public: string name; int day; }; //抽象责任链, 抽象接口. 稳定类. //不稳定的变化的接口交由子类重写 class IHandler { public: void SetNextHandler(IHandler* _next) { next = _next; } //Handle是一个稳定的算法骨架子, TemplateMethod bool Handle(const Context& ctx) { if (CanHandle(ctx)) { HandleRequest(ctx); } else if (GetNextHandler()) { return GetNextHandler()->HandleRequest(ctx); } else { //err 出错了,链到底了都没有处理 cerr << "责任链无法处理" << endl; } } protected: virtual bool CanHandle(const Context& ctx) = 0;//能不能handle也交由子类决定 virtual bool HandleRequest(const Context& ctx) = 0;//具体的handle方法由子类实现 IHandler* GetNextHandler() { return next; } virtual ~IHandler() {} private: IHandler* next; }; //继承重写扩展出具体的请求处理类 class HandleByMainProgram : public IHandler { protected: virtual bool HandleRequest(const Context& ctx) { // return true; } virtual bool CanHandle(const Context& ctx) { // return true; } }; class HandleByProjMgr : public IHandler { protected: virtual bool HandleRequest(const Context& ctx) { // return true; } virtual bool CanHandle(const Context& ctx) { // return true; } }; class HandleByBoss : public IHandler { protected: virtual bool HandleRequest(const Context& ctx) { // return true; } virtual bool CanHandle(const Context& ctx) { // return true; } }; int main() { //使用方式 IHandler* h1 = new HandleByMainProgram(); IHandler* h2 = new HandleByProjMgr(); IHandler* h3 = new HandleByBoss(); h1->SetNextHandler(h2); h2->SetNextHandler(h3); // 设置下一指针 Context ctx; h1->Handle(ctx); return 0; }
对比前后优势何在, 运用了哪些设计原则在其中?
开放封闭原则, 对扩展开放, 对修改关闭, 将扩展通过虚函数重写的方式延迟到子类实现.
单一职责原则, 每一个子类仅仅只是将虚函数部分按照自己的职责进行重写,而不是将多个职责合在一个大类中, 将职责分散到多个子类中单独实现 (类职责单一.)
封装变化点,将变化部分定义为virtual函数接口抽象到基类中, 基类是一个稳定类, 包含了稳定的Handle算法骨架子.
还涵盖了TemplateMethod在其中, 算法骨架稳定点往上提到基类中, 基类中包含的都是些稳定的方法和稳定的抽象接口, 模板方法如下, 算法细节延迟到子类实现 HandleRequest算法 + CanHandle算法细节都延迟到子类中实现.
//Handle是一个稳定的算法骨架子, TemplateMethod bool Handle(const Context& ctx) { if (CanHandle(ctx)) { HandleRequest(ctx); } else if (GetNextHandler()) { return GetNextHandler()->HandleRequest(ctx); } else { //err 出错了,链到底了都没有处理 cerr << "责任链无法处理" << endl; } }
责任链要点学习
解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合
责任链强调请求最终由一个子处理流程处理, 通过各个子处理条件判断
责任链扩展就是功能链, 功能链强调的就是,一个请求沿着处理链依次处理,叠加处理,而不是选择处理
将职责和职责顺序进行抽象, 所以职责变化可以任意扩展, 同时职责顺序也可以任意扩展
本质:
分离职责, 抽象职责, 动态组合链接职责
至此,各位兄弟朋友们, 小杰相当于和大家一起对于设计模式的学习开了个头. 设计模式的学习绝对不是学习既定的设计模式,去死记硬背如何写,一味的强行使用设计模式,生拉硬拽的往固定的设计模式上去靠。 这个绝对不是学习设计模式的正确姿势
设计原则高于设计模式, 抓住稳定点,变化点,分析缺失的设计原则,分析学习具体的设计模式所用到的设计原则,这样才对我们真正有用
在空余时间对于已有的既定代码进行持续重构,在重构的中研究实现自己的设计模式才是绝对正确的方式
祝兄弟们越来越好,大家一起加油,升职加薪,学业有成