[C/C++后端开发学习] 6 设计模式:结构型

说明:本文内容主要摘自www.0voice.com课程内容

单例模式(Singleton Pattern)

定义

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

作用

单例模式提供了一种直接访问某个类唯一对象的方式。主要用于避免一个全局使用的类频繁地创建与销毁,从而节省系统资源。

代码

class Singleton
{
public:
    static Singleton & getInstance()
    {
        static Singleton instance;  // 此处要保证线程安全必须使用c++11
        return instance;
    }

private:
    Singleton(){}	// 构造函数为private,用户无法调用
    ~Singleton(){}
    Singleton(const Singleton & s){}
    Singleton & operator=(const Singleton & s){}
};

这是最简洁的实现方式,但必须依赖c++11的 magic static 特性来保证多线程环境下静态变量初始化的线程安全问题。如果在C++98上实现则必须对静态变量初始化时加互斥锁并操作内存屏障。

这种方式的优点在于:

  • 利⽤静态局部变量特性,实现延迟加载;
  • 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数;
  • 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;
  • c++11 静态局部变量初始化时,具备线程安全特性;

c++11 magic static 特性
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。

Meyers

内存屏障
获取内存屏障可防止CPU指令重排。

这个版本的拓展性还是比较差的,一种更好的写法可以使单例模式通过继承更方便地运用到其他类,以达到扩展的目的。

template<typename T>
class Singleton
{
public:
    static T & getInstance()
    {
        static T instance;
        return instance;
    }
protected:
    Singleton(){}
    virtual ~Singleton(){}
    Singleton(const Singleton & s){}
    Singleton & operator=(const Singleton & s){}
};

class SingletonObj : public Singleton<SingletonObj>
{
    friend class Singleton<SingletonObj>;   // 需要定义为友元,父类才能调用子类构造函数、析构函数

public:
    ~SingletonObj(){}

private:
    SingletonObj(){}    // 构造函数为private,用户无法调用
    SingletonObj(const SingletonObj & s){}
    SingletonObj & operator=(const SingletonObj & s){}
};

工厂方法

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。 ——《设计模式》GoF

相比于简单工厂,工厂方法可以符合开闭原则。

要点

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

工厂类往往还以单例模式实现。

举例

实现导出数据的接口, 导出数据的格式包含 xml,json,txt 后面可能扩展excel格式csv

代码实现

定义导出接口并根据需求实现不同的子类(不同产品)

// 定义接口
class IExport
{
public:
    IExport(){}
    virtual ~IExport(){}
    virtual bool exportData(const string & data) = 0;
};

class ExportXml : public IExport
{
public:
    ExportXml(){}
    virtual ~ExportXml(){}
    virtual bool exportData(const string & data)
    {
        // 执行具体的导出数据流程
        cout << "# export xml data - " << data << endl;
        return true;
    }
};

class ExportJson : public IExport
{
public:
    ExportJson(){}
    virtual ~ExportJson(){}
    virtual bool exportData(const string & data)
    {
        // 执行具体的导出数据流程
        cout << "# export json data - " << data << endl;
        return true;
    }
};

class ExportTxt : public IExport
{
public:
    ExportTxt(){}
    virtual ~ExportTxt(){}
    virtual bool exportData(const string & data)
    {
        // 执行具体的导出数据流程
        cout << "# export txt data - " << data << endl;
        return true;
    }
};

然后再定义工厂,也是先定义接口再实现子类,并且工厂通过产品的指针来实现组合。这里的代码对子类又实现了单例模式。

class ExportFactory
{
public:
    ExportFactory() : _export(nullptr) {}
    virtual ~ExportFactory()
    {
        if(_export != nullptr)
        {
            delete _export;
            _export = nullptr;
        }
    }

    virtual bool exportData(const string & data)
    {
        if(_export == nullptr)
            _export = getExport();	// 调用子类的getExport()来获取当前工厂对应的产品
        _export->exportData(data);
    }

protected:
    virtual IExport* getExport() = 0;  // 获取工厂对应的export产品

private:
    IExport* _export;
};

class FactoryExportXml : public ExportFactory
{
    friend class ExportFactory;
public:
    static FactoryExportXml & getFactory()
    {
        static FactoryExportXml f;
        return f;
    }

private:
    virtual IExport* getExport()
    {
        // 产生 xml 的export前可能有其它操作,比如初始化一些接口
        IExport * temp = new ExportXml;
        // 之后可能还有一些操作,比如设置一些参数
        return temp;
    }
    FactoryExportXml(){}
    virtual ~FactoryExportXml(){}
};

class FactoryExportJson : public ExportFactory
{
    friend class ExportFactory;
public:
    static FactoryExportJson & getFactory()
    {
        static FactoryExportJson f;
        return f;
    }

private:
    virtual IExport* getExport()
    {
        // 产生 xml 的export前可能有其它操作,比如初始化一些接口
        IExport * temp = new ExportJson;
        // 之后可能还有一些操作,比如设置一些参数
        return temp;
    }
    FactoryExportJson(){}
    virtual ~FactoryExportJson(){}
};

class FactoryExportTxt : public ExportFactory
{
    friend class ExportFactory;
public:
    static FactoryExportTxt & getFactory()
    {
        static FactoryExportTxt f;
        return f;
    }

private:
    virtual IExport* getExport()
    {
        // 产生 xml 的export前可能有其它操作,比如初始化一些接口
        IExport * temp = new ExportTxt;
        // 之后可能还有一些操作,比如设置一些参数
        return temp;
    }
    FactoryExportTxt(){}
    virtual ~FactoryExportTxt(){}
};

测试代码:

int main()
{
    ExportFactory* factory = &FactoryExportJson::getFactory();
    factory->exportData("xxxxx");

    factory = &FactoryExportXml::getFactory();
    factory->exportData("jjjjj");

    factory = &FactoryExportTxt::getFactory();
    factory->exportData("ttttt");

    return 0;
}

输出打印:

# export json data - xxxxx
# export xml data - jjjjj
# export txt data - ttttt

本质

延迟到子类来选择实现

抽象工厂

定义

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

一个超级工厂创建其他工厂。

结构图

在这里插入图片描述

举例

实现一个拥有导出导入数据的接口(两种产品),让客户选择数据的导出导入方式

代码实现

实际上我们在上面工厂方法中实现的就已经属于一般意义上的抽象工厂了,只不过其中只有一种工厂(生产export接口)。可以再增加针对import的工厂即可。

定义import接口:

class IImport
{
public:
    IImport(){}
    virtual ~IImport(){}
    virtual bool importData(const string & data) = 0;
};

具体的IImport子类与export接口类似,此处就不再写出。定义一个同时支持导入与导出的工厂类:

class DataAPIFactory
{
public:
    DataAPIFactory() : _export(nullptr), _import(nullptr) {}
    virtual ~DataAPIFactory()
    {
        if(_export != nullptr)
        {
            delete _export;
            _export = nullptr;
        }
        if(_import != nullptr)
        {
            delete _import;
            _import = nullptr;
        }
    }

    virtual bool exportData(const string & data)
    {
        if(_export == nullptr)
            _export = getExport();
        _export->exportData(data);
    }
    virtual bool importData(const string & data)
    {
        if(_import == nullptr)
            _import = getImport();
        _import->importData(data);
    }

protected:
    virtual IExport* getExport() = 0;  // 获取工厂对应的export产品
    virtual IImport* getImport() = 0;  // 获取工厂对应的import产品

private:
    IExport* _export;
    IImport* _import;
};

之后的处理就在导出接口的基础上增加导入接口就行。

责任链

定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ——《设计模式》GoF

要点

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

举例

请假流程审批:1 天内需要主管批准,3 天内需要项目经理批准,3 天以上需要老板批准……

显然,加入请假时间在3天内,则主管环节将不会处理,项目经理这一环节将进行处理,处理完成后责任链就结束了,不会再将请求传递到老板。这是责任的特点。

代码示例

首先定义责任链中每一个环节的接口。Context 类仅作为相关参数传递。

class Context {
public:
    Context(const string& n, const int& d) : name(n), day(d) {}
    string name;
    int day;
};

class IHandler
{
public:
    IHandler() : next(nullptr) {}
    virtual ~IHandler(){}

    void setNextHandler(IHandler* h)
    {
        next = h;
    }

    bool handle(const Context& context)
    {
        if(canHandle(context))
            handleRequest(context);
        else if(getNextHandler())
            getNextHandler()->handle(context);  // 传递到下一级
        else
            cout << "error: no one can handle.\n";
    }

protected:
    virtual IHandler* getNextHandler()
    {
        return next;
    }

    virtual bool canHandle(const Context& context) = 0;
    virtual bool handleRequest(const Context& context) = 0;

private:
    IHandler* next;
};

接着实现责任链上各环节:

class supervisorHandler : public IHandler
{
public:
    supervisorHandler(){}
    virtual ~supervisorHandler(){}

    virtual bool canHandle(const Context& context)
    {
        return context.day <= 1;
    }

    virtual bool handleRequest(const Context& context)
    {
        /* ...... */
        cout << "# supervisor evaluating...\n";

        return true;
    }
};

class prjManagerHandler : public IHandler
{
public:
    prjManagerHandler(){}
    virtual ~prjManagerHandler(){}

    virtual bool canHandle(const Context& context)
    {
        return context.day <= 3;
    }

    virtual bool handleRequest(const Context& context)
    {
        /* ...... */
        cout << "# prjManager evaluating...\n";

        return true;
    }
};

class bossHandler : public IHandler
{
public:
    bossHandler(){}
    virtual ~bossHandler(){}

    virtual bool canHandle(const Context& context)
    {
        return true;
    }

    virtual bool handleRequest(const Context& context)
    {
        /* ...... */
        cout << "# boss evaluating...\n";

        return true;
    }
};

测试代码:

int main()
{
    Context cj("John", 2);
    Context cm("Maria", 4);

    IHandler* h0 = new supervisorHandler;
    IHandler* h1 = new prjManagerHandler;
    IHandler* h2 = new bossHandler;
    // 建链
    h0->setNextHandler(h1);
    h1->setNextHandler(h2);

    // 处理
    bool result = h0->handle(cj);
    cout << cj.name << " submit a context and result is " << result << endl;
    result = h0->handle(cm);
    cout << cm.name << " submit a context and result is " << result << endl;

	// delete 未写出
    return 0;
}

打印输出:

# prjManager evaluating...
John submit a context and result is 1
# boss evaluating...
Maria submit a context and result is 1

装饰器模式

定义

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

在这里插入图片描述

要点

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

本质

动态组合

装饰器和责任链的区别: 装饰器的迭代与顺序无关,而责任链必须注意处理顺序;责任链中只有一个子流程进行处理,并在处理结束后打断流程,而装饰器所有子流程都会执行。

举例

普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能针对不同的职位产生不同的奖金组合。
- 销售奖金 = 当月销售额 * 4%
- 累计奖金 = 总的回款额 * 0.2%
- 部门奖金 = 团队销售额 * 1%
- 环比奖金 = (当月销售额-上月销售额) * 1%

代码实现

实现基本工资的计算器,Context 类仅作为相关参数传递:

class Context {
public:
    Context(double sa = 0.0, double p = 0.0, double gs = 0.0, bool isM = false) : 
        salesAmount(sa), payment(p), groupSales(gs), isMgr(isM) {}

    bool isMgr;
    double salesAmount;
    double payment;
    double groupSales;
};

class BonusCalculator   // Base
{
public:
    BonusCalculator(BonusCalculator* bc = nullptr) : additional(bc) {}
    ~BonusCalculator(){};

    virtual double calc(const Context& con)
    {
        cout << "# Calc BaseBonus\n";
        return 0.0;
    }

protected:
    BonusCalculator* additional;	// 链表的next
};

基于BonusCalculator扩展其他工资计算器,仅需重写 calc() 方法:

class MonthBonusCalculator : public BonusCalculator
{
public:
    MonthBonusCalculator(BonusCalculator* bc = nullptr) : BonusCalculator(bc) {}
    ~MonthBonusCalculator(){};

    virtual double calc(const Context& con)
    {
        cout << "# Calc MonthBonus\n";
        double bonus /* = ......计算方法略*/;
        return additional ? (bonus + additional->calc(con)) : bonus;
    }
};

class SalesAmountBonusCalculator : public BonusCalculator
{
public:
    SalesAmountBonusCalculator(BonusCalculator* bc = nullptr) : BonusCalculator(bc) {}
    ~SalesAmountBonusCalculator(){};

    virtual double calc(const Context& con)
    {
        cout << "# Calc SalesAmountBonus\n";
        double bonus /* = ......计算方法略*/;
        return additional ? (bonus + additional->calc(con)) : bonus;
    }
};

class GroupBonusCalculator : public BonusCalculator
{
public:
    GroupBonusCalculator(BonusCalculator* bc = nullptr) : BonusCalculator(bc) {}
    ~GroupBonusCalculator(){};

    virtual double calc(const Context& con)
    {
        cout << "# Calc GroupBonus\n";
        double bonus /* = ......计算方法略*/;
        return additional ? (bonus + additional->calc(con)) : bonus;
    }
};

测试代码:

int main()
{
    Context c;
    BonusCalculator* bc = new BonusCalculator;
    BonusCalculator* mbc = new MonthBonusCalculator(bc);
    BonusCalculator* sabc = new SalesAmountBonusCalculator(mbc);
    BonusCalculator* gbc = new GroupBonusCalculator(sabc);

    double bonus = gbc->calc(c);
    return 0;
}

打印输出:

# Calc GroupBonus
# Calc SalesAmountBonus
# Calc MonthBonus
# Calc BaseBonus
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值