一、单例模式
版本一
我们来看看第一种单例模式
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
}
return _instance;
}
private:
//放在private里边是为了调用默认的初始化与释放的操作
Singleton() {} //构造,放在private是为了保证全局只有一个实例
~Singleton() {} //析构函数放在private当中的原因是避免我们直接去delete它,否则在使用的时候容易变成野指针
Singleton(const Singleton &clone){} //拷⻉构造
Singleton& operator=(const Singleton&) {}
static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
第一个版本我们来思考一下,这个没有考虑到哪些问题
线程安全
还有一个问题就是,在new这个对象的时候,是放在堆区的,这个对象是静态的放在全局区,我们的单例在进程退出的时候,析构函数是调不到的,所以会有一个内存泄漏的风险。这是一个多线程的风险,属于线程安全问题
所以我们引出版本二
版本二
这个版本二,主要是解决单例的全局静态变量的释放问题
// 类对象之间是友元的
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
atexit(Destructor); //对象在使用完以后会被释放掉,这个函数是在main函数结束之后调用的
}
return _instance;
}
~Singleton() {}
private:
static void Destructor() {
if (nullptr != _instance) {
// delete _instance;
_instance = nullptr;
}
}
Singleton();//构造
~Singleton() {}
Singleton(const Singleton &cpy); //拷⻉构造
Singleton& operator=(const Singleton& other) {}
static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;
//静态成员需要初始化
// 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题
我们还有多线程问题需要解决
版本三
多线程问题的解决方式就是加锁,3.1这个锁是为了避免这个对象被重复创建,但是有一个性能问题。比如说我们使用10000次这种单例模式,只有一次是需要加这个锁去创建对象的。这个锁没有问题,操作就太重了,我们用的是互斥锁,在切换线程的时候,会降低我们获取这个单例的性能,我们把3.1锁注释掉。
我们来看3.2,这也是有问题的,可能会出现两次new的情况,第一次使用的时候,会有两个线程同时到达,因为两个线程同时在等待的话,会有一个线程进去,就会被new两次,所以就要使用一个if判断语句
#include <mutex>
class Singleton { // 懒汉模式 lazy load
public:
static Singleton * GetInstance() {
// std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换线程
if (_instance == nullptr) {
std::lock_guard<std::mutex> lock(_mutex); // 3.2
if (_instance == nullptr) {
_instance = new Singleton(); // 1. 分配内存 // 2. 调用构造函数 // 3. 返回指针 // 多线程环境下 cpu reorder操作
atexit(Destructor);
}
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) {
delete _instance;
_instance = nullptr;
}
}
Singleton(){} //构造
Singleton(const Singleton &cpy){} //拷⻉构造
Singleton& operator=(const Singleton&) {}
static Singleton * _instance;
static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
std::mutex Singleton::_mutex; //互斥锁初始化
版本三考虑了一个多线程问题,但是有个特殊情况,我们先屡一下对象创建的过程,1、分配内存。2、调用构造函数 3、返回对象指针
这里就涉及到多线程环境下的问题
多线程环境下cpu有一个reorder操作,会帮我们自动优化上边创建对象的三个步骤,可能我们执行的顺序会被打乱。
所以我们有时候去gdb调试代码的时候,会发现我们构造的对象的值是一些乱码的值。
在java中有一个关键字叫valitile来进行内存屏障,可以解决问题
版本四
这里我们需要用到内存屏障,这里就使用到了C++11的内存屏障,获取内存屏障以后会使得new的这个操作会安装顺序执行,就解决了内存重排的问题。
// volitile
#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; //互斥锁初始化
// g++ Singleton.cpp -o singleton -std=c++11
版本五
这里为什么没有多线程问题呢,主要是在C++11当中,static有一个magic的特性,如果有多线程进来的话,并发线程会阻塞等待初始化结束,静态变量只能被初始化一次,只会有一个线程进去,也没有cpu reorder的问题。
析构函数也是能调用成功的,因为静态是可以自动调用析构函数的,只有在堆上才需要手动去释放
// c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将 会阻塞等待初始化结束。
// c++ effective
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance;//采用静态对象的一种方式
return instance;
}
private:
Singleton(){}
~Singleton() {}
Singleton(const Singleton&) {}
Singleton& operator=(const Singleton&) {}
};
// 继承 Singleton
// g++ Singleton.cpp -o singleton -std=c++11
/*该版本具备 版本5 所有优点:
1. 利⽤静态局部变量特性,延迟加载;
2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数;
3. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;
4. c++11 静态局部变量初始化时,具备线程安全;
5. */
这个方法的局限性在于扩展性比较差,如果一个项目有多个单例就比较麻烦,每个单例都要去这样写。
所以我们考虑使用模板
版本六
template<typename T>
class Singleton {
public:
static T& GetInstance() {
static T instance; // 这⾥要初始化DesignPattern,需要调⽤DesignPattern 构造函数,同时会调⽤⽗类的构造函数。
return instance;
}
protected:
virtual ~Singleton() {}
Singleton() {} // protected修饰构造函数,才能让别⼈继承
Singleton(const Singleton&) {}
Singleton& operator =(const Singleton&) {}
};
class DesignPattern : public Singleton<DesignPattern> {
friend class Singleton<DesignPattern>; // friend 能让Singleton<T> 访问到 DesignPattern构造函数
public:
~DesignPattern() {}
private:
DesignPattern() {}
DesignPattern(const DesignPattern&) {}
DesignPattern& operator=(const DesignPattern&) {}
};
采用这种继承的方式可避免重复代码
手撕单例模式可以用版本5,记性好就使用版本6
二、工厂方法
定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。 ——《设计模式》GoF
背景
实现一个导出数据的接口,让客户选择数据的导出方式;
要点
解决创建过程比较复杂,希望对外隐藏这些细节的场景;
比如连接池、线程池
隐藏对象真实类型;
对象创建会有很多参数来决定如何创建;
创建对象有复杂的依赖关系;
本质
延迟到子类来选择实现;
版本一
这个版本,是在父类的基础上去写这个子类,用子类去实现不同的方法
#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;
}
};
// csv
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;
// }
// };
// =====1
int main() {
std::string choose/* = */;
if (choose == "txt") {
/***/
IExport *e = new ExportTxt();
/***/
e->Export("hello world");
} else if (choose == "json") {
/***/
IExport *e = new ExportJson();
/***/
e->Export("hello world");
} else if (choose == "xml") {
IExport *e = new ExportXml();
e->Export("hello world");
} else if (choose == "csv") {
IExport *e = new ExportXml();
e->Export("hello world");
}
}
版本二
#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 IExportFactory {
public:
virtual IExport * NewExport(/* ... */) = 0;
};
//根据这个来生成不同工厂方法的子类
class ExportXmlFactory : public IExportFactory {
public:
IExport * NewExport(/* ... */) {
// 可能有其它操作,或者许多参数
IExport * temp = new ExportXml;
// 可能之后有什么操作
return temp;
}
};
class ExportJsonFactory : public IExportFactory {
public:
IExport * NewExport(/* ... */) {
// 可能有其它操作,或者许多参数
IExport * temp = new ExportJson;
// 可能之后有什么操作
return temp;
}
};
class ExportTxtFactory : public IExportFactory {
public:
IExport * NewExport(/* ... */) {
// 可能有其它操作,或者许多参数
IExport * temp = new ExportTxt;
// 可能之后有什么操作
return temp;
}
};
class ExportData {
public:
ExportData(IExportFactory *factory) : _factory(factory) {}
~ExportData() {
if (_factory) {
delete _factory;
_factory = nullptr;
}
}
bool Export(const std::string &data) { // 稳定的流程 往基类放
IExport * e = _factory->NewExport();
e->Export(data);
}
private:
IExportFactory *_factory;
};
int main() {
ExportData ed(new ExportTxtFactory);
//屏蔽了所有实现细节,直接调用接口
ed.Export("hello world");
return 0;
}
抽象工厂
定义
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。——《设计模式》GoF
背景
实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式;
结构图
#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;
}
};
// 对于初学者: 知道扩展代码
// 5年
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;
}
责任链
定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ——《设计模式》GoF
背景
请求流程,1 天内需要主程序批准,3 天内需要项目经理批准,3 天以上需要老板批准;
要点
解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;
责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;
责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;
将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
稳定点就是流程是稳定的
天数是变化点
#include <string>
class Context {
public:
std::string name;
int day;
};
// 稳定点 和 变化点
// 稳定点:批假流程 由职位从低到高批假 有权限
// 变化点:请假天数、增加批假人员
// 在一个整洁的房子(稳定点),好动的猫(变化),怎么保证这个房子的整洁?
// 把猫放在笼子里
// 如果某一个需求 只有稳定点 不需要设计模式(应对变化的)
// 如果全是变化点,也不需要设计模式?软件开发怎么解决全是变化点?脚本语言 热更新 来应对变化点
// 如果两者都存在,不是消灭变化点,是隔离变化点 解耦合
// 多线程锁 锁的类型 锁的粒度 线程池
class LeaveRequest {
public:
bool HandleRequest(const Context &ctx) {
if (ctx.day <= 1)
HandleByBeaty(ctx);
if (ctx.day <= 1)
HandleByMainProgram(ctx);
else if (ctx.day <= 10)
HandleByProjMgr(ctx);
else
HandleByBoss(ctx);
}
public:
bool HandleByBeaty(const Context &ctx) {
}
bool HandleByMainProgram(const Context &ctx) {
}
bool HandleByProjMgr(const Context &ctx) {
}
bool HandleByBoss(const Context &ctx) {
}
};
#include <string>
class Context {
public:
std::string name;
int day;
};
// 稳定点抽象,变化点扩展
class IHandler {
public:
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 HandleByMainProgram : public IHandler {//主程处理
protected:
virtual bool HandleRequest(const Context &ctx){
//
return true;
}
virtual bool CanHandle(const Context &ctx) {
//
if (ctx.day <= 10)
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() {
// IHandler * h1 = new HandleByMainProgram();
// IHandler * h2 = new HandleByProjMgr();
// IHandler * h3 = new HandleByBoss();
// h1->SetNextHandler(h2);
// h2->SetNextHandler(h3);
// 抽象工厂
// nginx http 处理 ,也用的责任链
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);
// 设置下一指针
Context ctx;
h0->Handle(ctx);
return 0;
}
例如我们的http协议的处理流程,就可以使用责任链模式
装饰器
定义
动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活。
—— 《设计模式》GoF
背景
普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖
金,同时可能针对不同的职位产生不同的奖金组合;
稳定点是扩展流程
变化点就是待遇的多少
要点
通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
不是解决“多子类衍生问题”问题,而是解决“父类在多个方向上的扩展功能”问题;
装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能;
本质
动态组合
#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 *cb2 = new CalcSumBonus(base);
CalcBonus *cb1 = new CalcMonthBonus(cb2);
cb2->Calc(ctx1);
// 2. 部门经理
Context ctx2;
CalcBonus *cb3 = new CalcGroupBonus(cb2);
cb3->Calc(ctx2);
}