C++设计模式-结构型设计模式

写少量的代码来应对未来需求的变化。

单例模式

定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。——《设计模式》GoF

解决问题

稳定点:

  • 类只有一个实例,提供全局的访问点(抽象)

变化点:
有多个类都是单例,能不能复用代码(扩展中的继承和组合)

代码结构

  • 私有的构造和析构
  • 禁掉拷贝构造、拷贝赋值、移动构造、移动赋值
  • 静态类成员函数
  • 静态私有成员变量
  • 访问方式: Singleton:Getlnstance()

版本一

把构造函数和析构函数私有化,让别人不能调用它
因为只有一个示例,所以我们要限定他的构造函数
并且提供一个全局访问点

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;//静态成
员需要初始化

存在问题_instance是静态成员,它没有释放,没有delete这个,然后我们看版本二

版本二

针对上面问题版本二可以主动调用 atexit(Destructor);

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;//静态成
员需要初始化
// 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题

存在问题:版本二不能够多线程的。

版本三

然后就用到了加锁,对于这个3.1和3.2很显然3.2这样效率更高,因为读的时候不需要加锁,只有写的时候才需要加锁。

#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.2if (_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(){};Singleton(const Singleton &) = delete; //拷⻉
构造Singleton& operator=(const Singleton&) =
delete;//拷贝赋值构造Singleton(Singleton &&) = delete;//移动构造Singleton& operator=(Singleton &&) =
delete;//移动拷贝构造static Singleton * _instance;static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr;//静态成
员需要初始化
std::mutex Singleton::_mutex; //互斥锁初始化

这里涉及到一个双重检测的问题:因为读的时候不需要加锁,只有写的时候才需要加锁,然后这里很巧妙,如果同时都进去第一个if语句的话,一开始只有一个能拿到锁,但是那个锁释放了,同时都进去第一层训话的那写也可以去申请,所以还要加上一个if,这就是双重检测

if (_instance == nullptr) {//双重检测std::lock_guard<std::mutex>
lock(_mutex); // 3.2if (_instance == nullptr) {//双重检测//操作,}
}

但是还是存在问题:
多核时代:它有一个编译器重排和cpu重排,然后让他以更快的速度执行,可能会违反顺序一致性。
然后C++为了解决这个问题,C++用同步原语,其中包括原子变量还要内存栅栏。
在这里插入图片描述
对于上面的代码,我们虽然加了一把锁,然后再new,但是我们没有考虑指令重排这个问题。因为这个new还要很多操作。1. 分配内存2. 调用构造函数3. 返回指针。再多个时代下,cpu可能会重排,因为再单线程的时代下,可能调用的是1,2,3.但是多线程可能调用的就是1,3,2。在其调用1,3之后就return了,然后下一个线程来了之后执行if是空的,然后它有调用了一次。

版本四

我们用原子语义,也就是无锁编程。
原子执行的问题:

  • 可见性问题
    • load 可以看到其他线程最新操作的数据
    • store 修改数据让其他线程可见
  • 执行序问题
    • 内存模型(memory_order_qcquire和memory_order_release)

内存阑珊

  • 可见性问题
  • 执行序问题
  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);}}

我们用原子变量解决原则性和可见性问题
内存栅栏解决执行序问题

// 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_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

多线程下无锁编程的单例模式就解决了。但是代码太长了。

版本五

我们直接构造一个static类型。
c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。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 静态局部变量初始化时,具备线程安全;
*/

版本六

这个就是实现一个多态,因为如果有多个单例,我们就不用都写那些重复的代码了。

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<DesignPattern> {friend class Singleton<DesignPattern>; //
friend 能让Singleton<T> 访问到 DesignPattern构造函数
private:DesignPattern() {}~DesignPattern() {}
};

结构图

在这里插入图片描述

工厂模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。 ——《设计模式》GoF
为什么要有工厂模式,而不直接使用new?
除了new,还有复杂构造流程

解决问题

稳定性:

  • 创建同类对象的接口 对象创造接口
  • 同类对象有一个相同的职责 功能接口

变化点:

  • 创建对象的扩展

代码结构

实现该功能:实现一个导出数据的接口,让客户选择数据的导出方式(xml,Json,txt,csv);

没有使用工厂模式:

#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 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;
}

对象创建接口

  • 创建具体对象
  • 调用功能接口

一个功能接口

设计原则
最小知道原则
面向接口编程

如何扩展代码

实现对象创建接口
实现功能接口
多态调用

总结

要点
解决创建过程比较复杂,希望对外隐藏这些细节的场景;

  • 比如连接池、线程池
  • 隐藏对象真实类型;
  • 对象创建会有很多参数来决定如何创建;
  • 创建对象有复杂的依赖关系;
    本质
    延迟到子类来选择实现;
    结构图
    在这里插入图片描述

抽象工厂模式

定义

提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。 ——《设计模式》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;}
};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;
}

工厂模式和抽象工厂模式的区别

  • 抽象工厂需要创建一系列功能对象(多个功能接口)
  • 工厂方法创建一类功能的对象

结构图

在这里插入图片描述

责任链

其中negix就是用到了责任链

定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ——《设计模式》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 <= 3)HandleByMainProgram(ctx);else if (ctx.day <= 10)HandleByProjMgr(ctx);elseHandleByBoss(ctx);}private: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() : next(nullptr) {}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;}// 通过函数来抽象 处理节点的个数  处理节点顺序static bool handler_leavereq(Context &ctx) {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);return h0->Handle(ctx);}
protected:virtual bool HandleRequest(const Context &ctx) {return true};virtual bool CanHandle(const Context &ctx) {return true};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 处理 // 设置下一指针 Context ctx;if (IHander::handler_leavereq(ctx)) {cout << "请假成功";} else {cout << "请假失败";}return 0;
}

设计原则

  • 组合优于继承
  • 面向接口编程
  • 接口依赖

扩展代码

实现处理接口

  • 针对增加节点

修改静态接口

  • 调整顺序
  • 添加节点或删除节点处理

总结

要点

  • 解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;
  • 责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;
  • 责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;
  • 将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;

本质
分离职责,动态组合;

结构图
在这里插入图片描述

装饰器

定义

动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活。 —— 《设计模式》GoF

解决问题

稳定点:

  • 顺序无关的增加职责
    变化点:
  • 增加

代码结构

普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能针对不同的职位产生不同的奖金组合;

没有设计模式

// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
class Context {
public:bool isMgr;// User user;// double groupsale;
};class Bonus {
public:double CalcBonus(Context &ctx) {double bonus = 0.0;bonus += CalcMonthBonus(ctx);bonus += CalcSumBonus(ctx);if (ctx.isMgr) {bonus += CalcGroupBonus(ctx);}return bonus;}
private:double CalcMonthBonus(Context &ctx) {double bonus/* = */;return bonus;}double CalcSumBonus(Context &ctx) {double bonus/* = */;return bonus;}double CalcGroupBonus(Context &ctx) {double bonus/* = */;return bonus;}
};int main() {Context ctx;// 设置 ctxBonus *bonus = new Bonus;bonus->CalcBonus(ctx);
}

装饰器模式

#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);
}

实现职责功能
在这里插入图片描述
protected 组合基类指针
在这里插入图片描述

通过继承基类扩展功能
在这里插入图片描述
通过依赖注入累加功能
在这里插入图片描述

设计原则

  • 组合优于继承
  • 面向接口编程
  • 接口依赖

扩展代码

  • 继承基类扩展功能
  • 通过依赖注入累加功能

总结

要点

  • 通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
  • 不是解决“多子类衍生问题”问题,而是解决“父类在多个方向上的扩展功能”问题;
  • 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能;

什么时候使用?
不影响其他对象的情况下,以动态、透明的方式给对象添加职责;每个职责都是完全独立的功能,彼此之间没有依赖;
本质
动态组合
结构图
在这里插入图片描述

组合模式

定义

将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

解决问题

稳定点
“具备层次关系”稳定的
对象和组合对象可统一使用
变化点
对象的职责变更
组合对象里对象数量变更

什么时候使用组合模式?

  • 如果你想表示对象的部分-整体层次结构,可以选用组合模式,
  • 把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也容易;
  • 如果你希望统一地使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能;

这种设计模式再游戏开发中特别常见。例如每一个用户玩家都有很多系统,比如说签到系统,宠物系统…。这些系统都会绑定在这个用户身上。然后这些宠物系统还有很多小功能。

代码结构

接口用于整合整体和部分的差异
在这里插入图片描述
叶子节点用于实现具体职责
在这里插入图片描述
组合节点职责委托叶子节点实现,同时具备组合叶子节点职责(最后执行的时候要给叶子节点执行)
在这里插入图片描述

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();}}
};

设计原则

  • 组合优于继承
  • 面向接口编程
  • 接口依赖

扩展代码

  • 继承接口
  • 实现职责
  • 组合整体和部分的关系
    怎么实现?
    将叶子节点当成特殊的组合对象看待,从而统一叶子对象和组合对象;
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/321196.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Day1| Java基础 | 1 面向对象特性

Day1 | Java基础 | 1 面向对象特性 基础补充版Java中的开闭原则面向对象继承实现继承this和super关键字修饰符Object类和转型子父类初始化顺序 多态一个简单应用在构造方法中调用多态方法多态与向下转型 问题回答版面向对象面向对象的三大特性是什么&#xff1f;多态特性你是怎…

关于在Conda创建的虚拟环境中安装好OpenCV包后,在Pycharm中依然无法使用且import cv2时报错的问题

如果你也掉进这个坑里了&#xff0c;请记住opencv-python&#xff01;opencv-python&#xff01;&#xff01;opencv-python&#xff01;&#xff01;&#xff01; 不要贪图省事直接在Anaconda界面中自动勾选安装libopencv/opencv/py-opencv包&#xff0c;或者在Pycharm中的解…

【Qt之OpenGL】01创建OpenGL窗口

1.创建子类继承QOpenGLWidget 2.重写三个虚函数 /** 设置OpenGL的资源和状态,最先调用且调用一次* brief initializeGL*/ virtual void initializeGL() override; /** 设置OpenGL视口、投影等&#xff0c;当widget调整大小(或首次显示)时调用* brief resizeGL* param w* para…

OpenCV如何为等值线创建边界旋转框和椭圆(63)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV 为轮廓创建边界框和圆(62) 下一篇:OpenCV的图像矩(64) 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 cv::minAreaRect使用 OpenCV 函数 cv::fitEllipse cv::min…

交易复盘-20240507

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 蔚蓝生物 (5)|[9:25]|[36187万]|4.86 百合花…

智慧工地的5大系统是什么?SaaS化大型微服务架构(智慧工地云平台源码)可多端展示登录

智慧工地解决方案依托计算机技术、物联网、云计算、大数据、人工智能、VR&AR等技术相结合&#xff0c;为工程项目管理提供先进技术手段&#xff0c;构建工地现场智能监控和控制体系&#xff0c;弥补传统方法在监管中的缺陷&#xff0c;最终实现项目对人、机、料、法、环的全…

基于Springboot的教学资源共享平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的教学资源共享平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

牛客NC320 装箱问题【中等 动态规划,背包问题 C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/d195a735f05b46cf8f210c4ad250681c 几乎完全相同的题目&#xff1a; https://www.lintcode.com/problem/92/description 思路 动态规划都是递归递推而来。php答案是动态规划版本&#xff0c;递归版本有 测试用…

Mybatis-Plus快速上手

依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version> </dependency> <dependency><groupId>mysql</groupId><artifactId&g…

如何配置Jupyter Lab以允许远程访问和设置密码保护

如何配置Jupyter Lab以允许远程访问和设置密码保护 当陪你的人要下车时&#xff0c;即使不舍&#xff0c;也该心存感激&#xff0c;然后挥手道别。——宫崎骏《千与千寻》 在数据科学和机器学习工作流中&#xff0c;Jupyter Lab是一个不可或缺的工具&#xff0c;但是默认情况下…

Jmeter分布式压测

一、jmeter为什么要做分布式压测 jmeter本身的局限性 一台压力机的 Jmeter 支持的线程数受限于 Jmeter 其本身的机制和硬件配置&#xff08;内存、CPU等&#xff09;是有限的由于 Jmeter 是 Java 应用&#xff0c;对 CPU 和内存的消耗较大&#xff0c;在需要模拟大量并发用户…

Instal IIS on Windows Server 2022 Datacenter

和以往版本一样&#xff0c;没有什么不同&#xff0c;So easy&#xff01; WinR - ServerManager.exe 打开服务器管理器&#xff0c;点击【添加角色和功能】&#xff0c;选择自己想要的角色和功能。 一、开始之前&#xff1a;帮助说明&#xff0c;点击【下一步】&#xff1b;…

OS复习笔记ch5-2

引言 在上一篇笔记中&#xff0c;我们介绍到了进程同步和进程互斥&#xff0c;以及用硬件层面上的三种方法分别实现进程互斥。其实&#xff0c;软件层面上也有四种方法&#xff0c;但是这些方法大部分都存在着一些问题&#xff1a; “上锁”与“检查”是非原子操作&#xff0…

纯血鸿蒙APP实战开发——手写绘制及保存图片

介绍 本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能。手写板上完成绘制后&#xff0c;通过调用image库的packToFile和packing接口将手写板的绘制内容保存为图片&#xff0c;并将图片文件保存在应用沙箱路径中。 效果图预览 使用说明 在虚线区域手写…

Java反序列化-CC11链

前言 这条链子的主要作用是为了可以在 Commons-Collections 3.2.1 版本中使用&#xff0c;而且还是无数组的方法。这条链子适用于 Shiro550漏洞 CC11链子流程 CC2 CC6的结合体 CC2 这是CC2的流程图&#xff0c;我们取的是后面那三个链子&#xff0c;但是由于CC2 只能在 c…

硬盘惊魂!文件夹无法访问怎么办?

在数字时代&#xff0c;数据的重要性不言而喻。然而&#xff0c;有时我们会遇到一个令人头疼的问题——文件夹提示无法访问。当你急需某个文件夹中的文件时&#xff0c;却被告知无法打开&#xff0c;这种感受真是难以言表。今天&#xff0c;我们就来深入探讨这个问题&#xff0…

下一代Nginx? OpenNjet 的入门实践

何为 OpenNjet &#xff1f; OpenNJet 应用引擎是基于 NGINX 的面向互联网和云原生应用提供的运行时组态服务程序&#xff0c;作为底层引擎&#xff0c;OpenNJet 实现了NGINX 云原生功能增强、安全加固和代码重构&#xff0c;利用动态加载机制可以实现不同的产品形态&#xff0…

机器学习第二天(监督学习,无监督学习,强化学习,混合学习)

1.是什么 基于数据寻找规律从而建立关系&#xff0c;进行升级&#xff0c;如果是以前的固定算式那就是符号学习了 2.基本框架 3.监督学习和无监督式学习&#xff1a; 监督学习&#xff1a;根据正确结果进行数据的训练&#xff1b; 在监督式学习中&#xff0c;训练数据包括输…

Python urllib 爬虫入门(1)

本文主要为Python urllib类库函数和属性介绍及一些简单示例。 目录 urllib爬取网页 简单示例 写入文件 其他读取方法 readline函数 readlines函数 response属性 当前环境信息 返回状态码 返回url地址 对url进行编码与解码 写入文件 总结 urllib爬取网页 通过pyth…

automa警惕通过点击元素打开新的标签页,因为你可能会被他蒙蔽!

大家好&#xff0c;我是大胡子&#xff0c;专注于研究RPA实战与解决方案。 我们经常用到automa里面的【点击元素】组件&#xff0c;但要警惕通过点击元素打开新的标签页&#xff0c;例如下面这个场景&#xff0c;点击公众号的图文消息&#xff0c;之后&#xff0c;要自动输入标…