网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
上述这款懒汉式的单例设计模式, 问题分析:
- 对于_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单例对象没有调用构造函数初始化以至于单例对象是随机值.
解决办法,参考CPU指令重排解决办法,内存屏障.
#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:
![img](https://img-blog.csdnimg.cn/img_convert/75372f97a51ed1e7f5238939dd40d74d.png)
![img](https://img-blog.csdnimg.cn/img_convert/405d73511353427c704071407ddc9c47.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
al ~TaxStrategy() {}
};
//扩展具体的税收策略
class CNTax : public TaxStrategy {
public:
virtual double CalculateTax(const Context& ctx) {
//...
}
};
class USTax : public TaxStrategy {
public:
[外链图片转存中...(img-PMWagBWj-1715826061955)]
[外链图片转存中...(img-g1AHS8t0-1715826061955)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**