一、单例模式
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;
}
//原子变量:解决原子性 可见性问题
//内存屏障:解决执行序问题
原子变量
一、它解决了三个问题
原子执行的问题
可见性的问题
①原子提供了load和store来解决。
②load:可以看见其他线程最新操作的数据。
③store:通常是写操作,修改了数据让其他线程可见。
执行序的问题
内存栅栏
保证上面或者下面的语句不能下去或上来。
内存顺序模型
版本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();
}
}
};