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.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(){};
    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.2
            if (_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);
        else
            HandleByBoss(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;
    // 设置 ctx
    Bonus *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.mfbz.cn/a/593215.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud微服务:Eureka 和 Nacos 注册中心

共同点 都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测 不同点 Nacos 支持服务端主动检测提供者状态&#xff1a;临时实例采用心跳模式&#xff0c;非临时&#xff08;永久&#xff09;实例采用主动检测模式Nacos 临时实例心跳不正常会被剔除&#xff0c;非临时实…

【uniapp】H5+、APP模拟浏览器环境内部打开网页

前言 今天将智能体嵌入到我的项目中&#xff0c;当作app应用时&#xff0c;发现我使用的webview组件&#xff0c;无论H5怎么登录都是未登录&#xff0c;而APP却可以&#xff0c;于是进行了测试&#xff0c;发现以下几种情况&#xff1a; 方法<a>标签webviewAPP✅✅网页…

YOLOv5改进之bifpn

目录 一、原理 二、代码 三、在YOLOv5中的应用 一、原理 论文链接:

课题学习(二十三)---三轴MEMS加速度计芯片ADXL372

声明&#xff1a;本人水平有限&#xff0c;博客可能存在部分错误的地方&#xff0c;请广大读者谅解并向本人反馈错误。 一、基础配置 测量范围-200g-200g&#xff0c;分辨率为12位&#xff0c; V s 、 V D D I / O V_s、V_{DDI/O} Vs​、VDDI/O​范围为1.6V-3.5V 1.1 引脚配…

【银角大王——Django课程——用户表的基本操作2】

用户表的基本操作2 编辑用户按钮删除按钮入职日期——不显示时分&#xff0c;只显示年月日——使用DataField函数不使用DateTimeField修改models记得重新执行命令&#xff0c;更新数据库结构修改前修改后 编辑用户按钮 点击编辑&#xff0c;跳转到编辑页面&#xff08;将编辑的…

CrossOver支持的软件多吗 CrossOver支持软件列表 crossover兼容性查询

如果你是一个喜欢在Mac上工作的用户&#xff0c;但又不想放弃一些Windows上的优秀软件&#xff0c;那么可以考虑使用一些兼容工具来运行Windows程序。其中&#xff0c;CrossOver就是一款功能强大且受欢迎的兼容工具。那么&#xff0c;CrossOver到底能支持哪些Windows软件呢&…

JVM笔记2--垃圾收集算法

1、如何确认哪些对象“已死” 在上一篇文章中介绍到Java内存运行时的各个区域。其中程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生&#xff0c;随线程而灭&#xff0c;栈中的栈帧随着方法的进入和退出而有条不紊的执行着入栈和出栈操作。每个栈帧中分配多少内存基本上…

VMvare如何更改虚拟机内共享文件夹的挂载点

更改虚拟机内共享文件夹的路径 进入目录 /etc/init.d ,并找到vmware-tools文件 里面有配置项 vmhgfs_mnt"/mnt/hgfs" 将引号内的内容更改为你需要挂载的路径,重启即可 注意挂载的路径不能是 “/”&#xff0c;必须根目录下的某个文件夹&#xff0c;或者其子文件夹 …

定时器编程前配置和控制LED隔一秒亮灭

1.配置定时器 0 工作模式16位计时 2.给初值&#xff0c;定一个10ms出来 3.开始计时

环形链表的判断方法与原理证明

&#xff08;题目来源&#xff1a;力扣&#xff09; 一.判读一个链表是否是环形链表 题目&#xff1a; 解答&#xff1a; 方法&#xff1a;快慢指针法 内容&#xff1a;分别定义快慢指针&#xff08;fast和slow&#xff09;&#xff0c;快指针一次走两步&#xff0c;慢指…

物体检测:如何检测小物体?

原文地址&#xff1a;https://medium.com/voxel51/how-to-detect-small-objects-cfa569b4d5bd 2024 年 4 月 22 日 物体检测是计算机视觉的基本任务之一。在高层次上&#xff0c;它涉及预测图像中物体的位置和类别。最先进的&#xff08;SOTA&#xff09;深度学习模型&#x…

3031087 -“无数据”:物料不显示在 MRP 应用中

症状 使用其中一个 MRP 应用&#xff08;监控物料覆盖范围、管理物料覆盖范围、监控外部需求等&#xff09;时无法找到物料。 用户在搜索过滤器时会收到错误消息“无数据”。 “本 KBA 中的图像/数据来自 SAP 内部系统、示例数据或演示系统。任何与真实数据相似的都是完全巧…

Apache反代理Tomcat项目,分离应用服务器和WEB服务器

项目的原理是使用单独的机器做应用服务器&#xff0c;再用单独的机器做WEB服务器&#xff0c;从网络需要访问我们的应用的话&#xff0c;就会先经过我们的WEB服务器&#xff0c;再到达应用程序&#xff0c;这样子的好处是我们可以保护应用程序的机器位置&#xff0c;同时还可以…

R语言中,查看经安装的包,查看已经加载的包,查看特定包是否已经安装,安装包,更新包,卸载包

创建于&#xff1a;2024.5.4 R语言中&#xff0c;查看经安装的包&#xff0c;查看已经加载的包&#xff0c;查看特定包是否已经安装&#xff0c;安装包&#xff0c;更新包&#xff0c;卸载包 文章目录 1. 查看经安装的包2. 查看已经加载的包3. 查看特定包是否已经安装4. 安装包…

java发送请求-http和https

http和https区别 1、http是网络传输超文本协议&#xff0c;client---- http------ server 2、httpshttpssl证书&#xff0c;让网络传输更安全 &#xff0c;client---- httpssl------ server 3、ssl证书是需要客户端认可的&#xff0c;注意官方证书和jdk生成的证书的用户来使…

实现批量自动文本标注(输出标签)代码复现

一&#xff1a;项目地址&#xff1a; IDEA-Research/Grounded-Segment-Anything: Grounded-SAM: Marrying Grounding-DINO with Segment Anything & Stable Diffusion & Recognize Anything - Automatically Detect , Segment and Generate Anything (github.com) 二…

3.SpringSecurity基本原理

SpringSecurity本质是一个过滤器链。十多个过滤器构成一个过滤器链。 这些过滤器在项目启动就会进行加载。每个过滤器执行放行操作才会执行下一个过滤器。 常见过滤器 FilterSecurityInterceptor 是一个方法级的权限过滤器&#xff0c;基本位于过滤器链的最底部。 Excepti…

内核workqueue框架

workqueue驱动的底半部实现方式之一就是工作队列&#xff0c;作为内核的标准模块&#xff0c;它的使用接口也非常简单&#xff0c;schedule_work或者指定派生到哪个cpu的schedule_work_on。 还有部分场景会使用自定义的workqueue&#xff0c;这种情况会直接调用queue_work和qu…

sql 中having和where区别

where 是用于筛选表中满足条件的行&#xff0c;不可以和聚类函数一起使用 having 是用于筛选满足条件的组 &#xff0c;可与聚合函数一起使用 所以having语句中不能使用select中定义的名字

QT:QT与操作系统

文章目录 信号槽与事件 信号槽与事件 在之前的信号槽中&#xff0c;已经有了一个基本的认识&#xff0c;那么对于QT中事件的理解其实就非常的类似&#xff0c;当用户进行某种操作的时候&#xff0c;就会触发事件&#xff0c;去执行一些对应的方法 QT对于事件又进行了封装&…