常用的设计模式

目录

什么是设计模式?

设计模式的基本原则

如何找到设计模式

常见的设计模式和使用场景

1. 模板模式

2. 观察者模式

3. 策略模式

4. 责任链模式

5. 装饰器模式

6. 单例模式

7. 工厂方法模式

8. 抽象工厂模式

9. 适配器模式

10. 代理模式

推荐书籍

技术参考


什么是设计模式?

设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。面向对象设计模式通常以类或者对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

设计模式的基本原则

1. 依赖倒置原则

高层模块不应该依赖低层模块,二者都应该依赖抽象;

抽象不应该依赖具体实现,具体实现应该依赖于抽象;

举个例子,自动驾驶系统公司是高层,汽⻋生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽⻋生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。

2. 开放封闭原则

一个类应该对扩展开放,对修改关闭。也就是对一个类尽量做扩展而不是修改,扩展一般是用继承或者组合的方式。

3. 面向接口编程

不将变量类型声明为某个特定的具体类,而是声明为某个接口。比如某个类成员是另一个类的具体对象,那么应该改成指向这个类的指针,因为根据多态原理,这个类指针实际上成为了一个接口。

客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。

减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。

4. 封装变化点

将稳定点和变化点分离,扩展修改变化点;让稳定点与变化点的实现层次分离。

5.  单一职责原则

一个类应该仅有一个引起它变化的原因。

6. 里式替换原则

子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可

能出现错误;覆盖了父类方法却没实现父类方法的职责。也就是子类如果声明了父类存在的函数 就要把功能实现完全,父类有的子类也一定要有。

7. 接口隔离原则

不应该强迫客户依赖于他们不用的方法。

一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责。

所以要正确使用public,protected,private这三种权限。

8. 对象组合优于类继承

继承耦合度高,组合耦合度低(继承不能换爹,组合可以换爹)。

如何找到设计模式

从重构中获得:

1. 静态转变为动态

2. 早绑定转为晚绑定

3. 继承转为组合

4. 编译时依赖转变为运行时依赖

5. 紧耦合转变为松耦合

掌握设计模式的原则比具体的设计模式更重要,只有当你对业务非常熟悉的时候,才能信手拈来的写出适合的设计模式。

常见的设计模式和使用场景

1. 模板模式

定义一个操作中的算法的⻣架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不 改变一个算法的结构即可重定义该算法的某些特定步骤。

下面来看一个例子:

class IGame
{
public:
    Play()
    {
        Process1();
        Process2();
        Process3();
        Process4();
    }
protected:
    virtual void Process1(){}
    virtual void Process2(){}
    virtual void Process3(){}
    virtual void Process4(){}
};

class Game1:public IGame
{
protected:
    virtual void Process2(){}
    virtual void Process4(){}
};

class Game2:public IGame
{
protected:
    virtual void Process1(){}
    virtual void Process3(){}
};

某个游戏IGame类有固定的流程,然后Game1和Game2分别继承自IGame,但是在具体某个步骤上各有不同,因此将Process1~4写成虚函数,由各个子类自己去实现。

这样写的前提当然是主框架流程是固定的,Play函数对外开放给用户使用,但是具体流程的函数是protected,提供子类去修改。这体现了接口隔离原则。

流程的接口在基类都抽象成了虚函数,由各个子类具体实现,这体现了依赖倒置原则。

2. 观察者模式

观察者模式在分布式架构中应用广泛,比如actor框架,skynet,redis,zoomkeeper,订阅和发布等。

观察者模式定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有 依赖于它的对象都得到通知并自动更新。

// 终端基类
class ITerminal 
{
public:
    virtual void show(int data){}
};

class DataCenter
{
public:
    void Attach(ITerminal* ob){} // 注册绑定
    void Detach(ITerminal* ob){} // 解绑
    void Notify()
    {
        for(auto it = obs.begin(); it != obs.end(); ++it)
        {
            it->show();
        }
    }
protected:
    virtual int GetData(){return  0;} // 获取数据
private:
    std::vector<ITerminal*> obs;
};

class Terminal1:public ITerminal
{
public:
    void show(int data){}
}
class Terminal2:public ITerminal
{
public:
    void show(int data){}
}

上面是一个发布和订阅的简单例子,假设有个数据中心提供数据给不同的终端展示,数据中心是一个稳定点,不同的终端设备是变化点,因此将终端封装成基类,提供一个展示的接口,不同的设备继承去实现不同的展示方式。

数据中心用一个vector或者set把注册过来的终端记录起来,需要展示的时候通过notify接口逐个通知展示。这样数据中心不必关注具体有哪些设备订阅了数据,是否订阅的操作交给了各个终端设备上。

3. 策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用 它的客户程序而变化。

策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法 之间进行切换; 策略模式消除了条件判断语句;就是在解耦合; 充分体现了开闭原则;单一职责; 其本质是分离算法,选择实现。

来看一个简单的例子,假设某电商网站一件商品在不同节假日的价格算法使不同的,可能会这么写:

enum VacationEnum
{
    chunjie, // 春节
    wuyi; // 五一
    qixi; // 七夕
    guoqing; // 国庆
};

class Context
{
};

class Promotion
{
    VacationEnum vac;
public:
    double calc()
    {
        if(vac == chunjie){}
        else if(vac == wuyi){}
        else if(vac == qixi){}
        else if(vac == guoqing){}
    }
};

这样写并不符合设计模式的单一职责原则和开放封闭原则。所以可以改成这样:

class Context
{};

// 变化的  扩展的
class BaseVac
{
public:
    virtual calc(Context &ctx){}
};
class Vacchunjie: public BaseVac
{
public:
    virtual calc(Context &ctx){}
};
class Vacwuyi: public BaseVac
{
public:
    virtual calc(Context &ctx){}
};
class Vacxiqi: public BaseVac
{
public:
    virtual calc(Context &ctx){}
};

// 稳定的 单一职责的
class Promotion
{
public:
    Promotion(BaseVac  *ss):s(ss){}
    double calc(Context &ctx)
    {
        s->calc(ctx);
    }
private:
    BaseVac  *s;
};

int main()
{
    BaseVac *v = new Vacxiqi();
    Promotion *p = new Promotion(v);
    
    return 0;
}

把变化点(不同节假日的不同算法)隔离出去,只保留单一的职责:获取价格。

这样做非常符合设计模式的原则,但是前提是对业务非常熟悉,因为别人来扩展维护的话,必须先了解之前已有的扩展有哪些。

4. 责任链模式

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

这个模式的一些要点:

a.解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;

b.责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;

c.责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;

d.充分体现了单一职责原则;将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展, 同时职责顺序也可以任意扩展;

本质是分离职责,动态组合。

责任链模式是比较过时的,在c语言里用的比较多,c++里有更好的数据结构(list)可以代替这个模式。

来看一个具体例子,公司请假审批规则是小于3天只给人事审批,大于3天且小于5天给经理审批,5天以上的给老板审批。下面是第一个版本的代码:

class Context
{
public:
    std::string name;
    int day;
};

class LeaveRequest
{
public:
    void HandleRequest(const Context &cxt)
    {
        if(cxt.day < 3)
        {
            HandleByHR(cxt);
        }
        else if(cxt.day < 5);
        {
            HandleByMgr(cxt);
        }
        else
        {
            HandleByBoss(cxt);
        }
        
    }
private: // 接口隔离原则
    void HandleByHR(const Context &cxt){}
    void HandleByMgr(const Context &cxt){}
    void HandleByBoss(const Context &cxt){}
};

怎么重构这个代码,从封装变化点的原则来看,先把HandleRequest里的各种条件拆分出来,然后新增一个类成员指针指向上一级,子类只需要判断自己是否应该处理,不能处理的就通过指针转移到上级去处理。

class Context
{
public:
    std::string name;
    int day;
};

class IHandler
{
public:
    virtual ~ IHandler(){}
    void SetNext(IHandler *n){bext = n;}
    virtual bool HandleRequest(const Context &cxt)
    {
        if(CanHandle(cxt))
        {
        }
        else if(GetNext())
        {
            GetNext()-> HandleRequest(cxt);
        }
        else
        {
            // error
        }
    }
protected:
    virtual bool CanHandle(const Context &cxt){}
    IHandler * GetNext(){return next;}
private:
    IHandler *next;
};

class HandleByMainProgram : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
    }
    virtual bool CanHandle() {
        //
    }
};

class HandleByProjMgr : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
    }
    virtual bool CanHandle() {
        //
    }
};
class HandleByBoss : public IHandler {
public:
    virtual bool HandleRequest(const Context &ctx){
        //
    }
protected:
    virtual bool CanHandle() {
        //
    }
};

int main () {
    IHandler * h1 = new MainProgram();
    IHandler * h2 = new HandleByProjMgr();
    IHandler * h3 = new HandleByBoss();
    h1->SetNextHandler(h2);
    h2->SetNextHandler(h3);

    Context ctx;
    h1->handle(ctx);
    return 0;
}

把不同部门的职责拆出来作为子类继承,主流程封装进基类里,上面这个版本同时使用了模板模式和责任链模式。

子类的接口都是protected的,这体现了接口隔离原则和依赖倒置原则。

责任链模式可以变形成功能链模式,比如nginx阶段处理,不同之处在于责任链每次只有一个模块会处理,功能链是按顺序执行不同的模块。

5. 装饰器模式

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

这个模式看起来会和责任链模式很像。

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

装饰器模式本质是动态组合。

举个例子,普通员工有销售奖金,累计奖金,部⻔经理除此之外还有团队奖金;后面可能会添加环比增⻓奖 金,同时可能针对不同的职位产生不同的奖金组合。那么代码可以这样写:

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

重构成装饰器模式后的代码:

class Context {
public:
    bool isMgr;
    // User user;
    // double groupsale;
};

// 试着从职责出发,将职责抽象出来
class CalcBonus {    
public:
    CalcBonus(CalcBonus * c = nullptr) {}
    virtual double Calc(Context &ctx) {
        return 0.0; // 基本工资
    }
    virtual ~CalcBonus() {}

protected:
    CalcBonus* cc;
};

class CalcMonthBonus : public CalcBonus {
public:
    CalcMonthBonus(CalcBonus * c) : cc(c) {}
    virtual double Calc(Context &ctx) {
        double mbonus /*= 计算流程忽略*/; 
        return mbonus + cc->Calc(ctx);
    }
};

class CalcSumBonus : public CalcBonus {
public:
    CalcSumBonus(CalcBonus * c) : cc(c) {}
    virtual double Calc(Context &ctx) {
        double sbonus /*= 计算流程忽略*/; 
        return sbonus + cc->Calc(ctx);
    }
};

class CalcGroupBonus : public CalcBonus {
public:
    CalcGroupBonus(CalcBonus * c) : cc(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

class CalcCycleBonus : public CalcBonus {
public:
    CalcGroupBonus(CalcBonus * c) : cc(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(cb2);
    cb3->Calc(ctx2);
}

在计算奖金的形式上,有点像递归,因为构造函数里设置的基类成员是给用户自己设置的,可以 灵活组合。而不是第一个版本那样用ifelse去判断是否要添加奖金 ,这样也很容易扩展和修改。

打个比方,责任链的模式看起来就像一个链表的结构,而装饰器模式看起来像一个俄罗斯套娃。

6. 单例模式

保证一个类仅有一个实例,并提供一个该实例的全局访问点。

先了解一个知识点,由C/C++编译的程序占用的内存分为以下几个部分:

1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。

2、堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。

4、文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区:存放函数体的二进制代码。

下面来看看几种实现单例模式的例子。

版本一:

  
#include <mutex>

class Singleton 
{ 
    // 懒汉模式 lazy load public:
    static Singleton * GetInstance() 
    {
        //std::lock_guard<std::mutex> lock(_mutex); // 这里加锁粒度太大 切换线程开销大 
        if (_instance == nullptr) 
        {
            std::lock_guard<std::mutex> lock(_mutex); // 在这里加锁
            if (_instance == nullptr) // 双重判断
            {
                _instance = new Singleton();
                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; //互斥锁初始化

这个例子有几个需要注意的点,第一个是加锁的粒度,当单例指针是空的时候再去加锁,减少锁粒度可以减少线程切换的开销。第二个是双重判断,防止多线程重入导致多次分配的问题。第三这是个懒汉模式,用到的时候才决定分配初始化。

但是这个例子有个问题。

c++的new操作里分为了三个步骤:分配内存,调用构造函数,赋值操作。在多线程环境下,cpu会进行指令重排,前面三个步骤可能执行起来是132的顺序。所以可能会出现某个线程判断单例指针不为空,但是还没调用构造函数,就立即返回,最后导致程序崩溃。所以上面这个例子还是有问题的。(如果在判断前就加锁是没问题的,代价是增加了开销)

版本二:

 
#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特性内存屏障来解决cpu指令重排的问题。

版本三:

class Singleton
{
public:
  ~Singleton(){}
  static Singleton& GetInstance() 
  {
    static Singleton instance;
    return instance;
  }
private:
  Singleton(){}
  Singleton(const Singleton&) {}
  Singleton& operator=(const Singleton&) {}
};
// g++ Singleton.cpp -o singleton -std=c++11

这个版本需要在c++11或者更高级版本的编译后才能用。

c++11 magic static 特性:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这就解决了多线程场景带来的问题。

该版本的优点:

a. 利用静态局部变量特性,延迟加载;

b. 利用静态局部变量特性,系统自动回收内存,自动调用析构函数;

c. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;

d. c++11 静态局部变量初始化时,具备线程安全;

版本四:

 
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构造函数
private:
    DesignPattern(){}
    DesignPattern(const DesignPattern&) {}
    DesignPattern& operator=(const DesignPattern&) {}
}
 

这个版本将单例模式封装成了模板,需要注意两个地方:

a.单例模板类的构造函数用protected修饰是为了它本身能被继承。

b. 单例模板里的GetInstance接口要获取类型T的构造函数,因此继承这个模板的类必须声明模板类为友元。

7. 工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化 延迟到子类。

一般这些业务会使用工厂模式:解决创建过程比较复杂,希望对外隐藏这些细节;比如连接池,线程池; 隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系;

线程池的初始化需要由机器的内核数来决定线程数量,但是用户并不需要关心内核数,用户只需要获取到可用的线程就行。因此可以用工厂模式。

其本质是延迟到子类来选择实现;

举个例子,实现一个导出数据的接口,让客户选择数据的导出方式:

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

// =====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");
    }
}

对于数据导出有多种不同格式的类,在业务代码层面上,第一个版本里需要使用者区分判断多种格式,我们希望把这种复杂的创建操作封装起来,因此创建一个工厂类,这个类绑定了导出数据的操作和指向具体数据格式的类成员,使用者只需要创建某个具体的工厂子类,调用导出的接口就完成任务了。

重构后的代码:

#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 ExportTxtFactory();
    factory->Export("hello world");
    return 0;
}

8. 抽象工厂模式

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

抽象工厂类是基于工厂类实现的,实际上是一回事,抽象工厂类拥有更多的更复杂的操作接口,但是对于使用者来说,仍然只需要创建时指定某个子工厂类,无需关心具体的操作内容。

举个例子,实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式:

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

9. 适配器模式

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起 工作的那些类可以一起工作。

原来的接口是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接口,让原来的接 口继续保持稳定,在子类通过组合的方式来扩展功能。

其本质是转换匹配,复用功能。

举个日志系统的例子,原来是通过写磁盘的方式进行存储,后来因为查询不便,需要额外添加往数据库写日志 的功能(写文件和数据库并存):

#include <string>
#include <vector>
using namespace std;

class LogSys {
public:
    LogSys() {}
    void WriteLog(const vector<string> &) {
        // ... 日志id 时间戳 服务器id 具体日志内容 roleid
    }
    vector<string>& ReadLog() {
        // ...
        vector<string> data /* = ...*/;
        return data;
    }
};

class DB;  // 面向接口编程 而不是具体类 强依赖  耦合性高  mysql mongo

class LogSysEx : public LogSys {
public:
    LogSysEx(DB *db) : _db(db) {}

    void AddLog(const vector<string> &data) {
        LogSys::WriteLog(data);
        /*
            这里调用 _db 的方法将 data 数据存储到数据库
        */
    }

    void DelLog(const int logid) {
        vector<string>& data = LogSys::ReadLog();
        // 从 vector<string> 中删除 logid的日志
        LogSys::WriteLog(data);
        // 调用 _db 的方法将 logid的日志删除
    }

    void UpdateLog(const int logid, const string &udt) {
        vector<string>& data = LogSys::ReadLog();
        // 从 vector<string> 中更新 logid的日志 udt
        LogSys::WriteLog(data);
        // 调用 _db 的方法将 logid的日志更改
    }

    string& LocateLog(const int logid) {
        vector<string>& data = LogSys::ReadLog();
        string log1 /* = from log file*/;
        string log2 /* = from db */;
        string temp = log1 + ";" + log2;
        return temp;
    }
    
private:
    DB* _db;
};

咱就是说在对一个类进行扩展修改的时候,尽量不要动原来的代码,而是通过继承组合的方式创建新的类,在这个新的类里实现新功能和包含旧功能。

10. 代理模式

代理模式是为其他对象提供一种代理以控制对这对象的访问。

比如远程代理(隐藏一个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护 代理(在代理前后做额外操作,权限管理,引用计数等); 在分布式系统中,actor模型(skynet)等会常用到代理模式。

其本质是控制对象访问。

举个例子,在有些系统中,为了某些对象的纯粹性,只进行了功能相关封装(稳定点),后期添加了其他功能 需要对该对象进行额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进行修改,这样 会让稳定点也变得不稳定),可以抽象一层代理层:


class ISubject {
public:
    virtual void Handle() = 0;
    virtual ~ISubject() {}
};

// 该类在当前进程,也可能在其他进程当中
class RealSubject : public ISubject {
public:
    virtual void Handle() {
        // 只完成功能相关的操作,不做其他模块的判断
    }
};

// 在当前进程当中  只会在某个模块中使用
class Proxy1 : public ISubject {
public:
    Proxy1(ISubject *subject) : _subject(subject) {}
    virtual void Handle() {
        // 在访问 RealSubject 之前做一些处理
        //if (不满足条件)
        //    return;
        _subject->Handle();
        count++;
        // 在访问 RealSubject 之后做一些处理
    }
private:
    ISubject* _subject;
    static int count;
};
int Proxy1::count = 0;

// 在分布式系统当中  skynet actor
class Proxy2 : public ISubject {
public:
    virtual void Handle() {
        // 在访问 RealSubject 之前做一些处理
        
        // 发送到数据到远端  网络处理  同步非阻塞 ntyco c协程
        //IResult * val = rpc->call("RealSubject", "Handle");

        // 在访问 RealSubject 之后做一些处理
    }
private:
    /*void callback(IResult * val) {
        // 在访问 RealSubject 之后做一些处理
    }*/
};

上面代码中proxy类可以继承ISubject类也可以不继承,写成继承的样式是为了让程序员明白这是该类的一个代理模块,如果不是继承的,看起来会和适配器模式很像。

proxy类中的基类指针可以改成一个容器,管理多个对象,这在游戏服务器的网关里经常看到。

以上就是c++常见的设计模式,个人感觉单例模式的多个版本实现都很巧妙,需要记住,其他模式一般都是基于对业务流程十分熟悉,并且在重构的基础上才能挑选出合适的模式。了解和掌握各种设计模式,对我们阅读别人的代码或者自己写代码都十分有帮助。

 推荐书籍

《设计模式-可复用面向对象软件的基础》

《重构与模式》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IIS5.1安装说明: 一: IIS 5.1版本适用于Windows XP_SP1、XP_SP2、XP_SP3 二: ISS 5.1安装步骤(各版本IIS都可以按照该方法安装,以IIS5.1安装至Windows xp_SP3为例) 1.依次打开 左下角的"开始"菜单----控制面板----选择"添加/删除程序",打开"添加/删除程序"窗体 ----点击窗体左侧"添加/删除Windows组件"(A) 2.解压IIS 压缩包,在打开的"Windows组件向导"窗体中,将"Internet 信息服务(IIS)"前面 小方框钩选上----点击下一步-----在弹出的"插入磁盘"窗体中,点击"确定"按扭------ 将解压后的IIS 5.1路径(如D:\飞翔下载\IIS\iis5.1xp)复制到"文件复制来源"输入框中, 如再遇到需要"插入光盘"之类的提示,继续粘贴该IIS路径即可,直到完成安装,当出现安装 完成提示按扭时,点击"完成"按扭关团向导,即可完成安装. 三: 问题及解决方案 1.IIS安装过程中,如果出现某些文件无法复制,则可能是该IIS不适合你的系统,请换一个 对应于系统的IIS,IIS版本说明如下. Windows XP_SP1 ,XP_SP2 ,XP_SP3 系统 适用 IIS5.1版本 Windows 2000 系统 适用 IIS5.0 版本 Windows server 2003系统 适用 IIS 6.0 版本 Windows Server 2008 ,Vista 系统 适用 IIS 7.0 版本 2.能完成IIS的安装即无法正常使用等问题,部分原因如下 可能与迅雷等网络工具TCP 端口产生冲突,打开控制面板----双击"性能和维护"打开--- "管理工具"----"Internet 信息服务"---"网站"---"选择"默认网站"右击---选择"属性" 菜单---在默认网站属性窗体分页框内选择"网站"标题---"TCP 端口" ----设为80即可完 成(IIS 默认80端口.如果使用其它端口,如8080,测试时需http://localhost:8080). 3.IIS 80端口被占用的解决方法 cmd 命令窗口----输入netstat -abn ->c:/port80.txt然后到c盘port80.txt文件中找到 占用80端口的程序pid,记下pid.打开任务管理器,点击"查看"/选择列,勾选"PID(进程标识 符)",然后单击"进程"标签,找到80端口对应的pid,就可以看到是哪个程序占用的了,更改 这个程序的port,再重启这个程序,使更改生效.再次启动iis.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值