C++设计模式之建造者模式

C++设计模式之建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  • C设计模式之建造者模式
    • 一缘由
    • 二实现
    • 三代码分析
    • 四总结

一、缘由

当我们在构造一个窗口控件的时候,往往包含三个方面的初始化工作:

  • UI初始化
  • 动画初始化
  • 信号槽初始化

这样我们就可以构造好一个窗口控件了,我们可以看以下类图:

C++设计模式之建造者模式_第1张图片

乍一看该实现并没有什么问题CenterWidget类在其构造函数中调用了initUiinitAnimationinitSlot三个私有成员函数分别进行Ui,动画,信号槽的初始化。可是当我们进行一个新的CenterWidget构造时,要求动画效果改变,这时候需要怎么做呢?修改initAnimation成员函数?作为一名优秀的程序员,提到修改类的时候就应该警惕,这个设计明显违反了开闭原则。这时候我们遇到到一个问题:构造的接口是固定的,构造的顺序是固定的,而要求构造的内容变化。
我们直觉上,这个问题的解就是:initUi,initAnimation,initSlot三个成员函数必须为虚函数。在需要动画、Ui或者信号槽发生变化的时候,只要需要添加新的子类,重写其中某个函数需要变化即可。可是,当这三个成员函数都成为虚函数之后,就不可能在构造函数中调用,因为
在一个类构造期间,vtable还没有初始化完成,虚函数机制不会正确工作。
于是我们增加了一个Init方法,在对象被构造出来之后调用之。此时类图如下:

C++设计模式之建造者模式_第2张图片

对于这个类图来说,解决了开闭原则的问题,可是新的问题出现了,客户端需要调用Init,有违反了接口隔离的和单一职责原则之嫌疑,为何这么说呢?
客户端的想法是:

  • 我想要一个CenterWidget实例
  • 然后将CenterWidget显示出来

CenterWidget提供的却是:

  • 给你一个CenterWidget实例
  • 然后调用Init接口初始化
  • 然后将CenterWidget显示出来

对于客户端来说,它需要依赖一个它不需要的接口Init,这个Init不是客户端所要求的,反而更象是客户端需要CenterWidget作为主窗口的逻辑功能而强行搭配的一个接口(注:接口隔离原则指的是客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。)。其次,作为主窗口类的CenterWidget需要额外负责管理复杂的构造逻辑。这倒不是什么严重的问题,很多时候这种解决方法都是行之有效的,不过我们提出一种更为优雅的解决方法。

二、实现

为了解决上节所述的问题,我们将整个CenterWidget构造职责抽取出来由单独的一个Builder类承担。这样将initUiinitAnimationinitSlot函数转移到Builder类中。由Builder类构造出来CenterWidget实例,另外,有时候对initUiinitAnimationinitSlot三个函数调用次序有所要求,引入一个Director类专门管理对initUiinitAnimationinitSlot的调用次序,指挥Builder的工作,这就是建造者模式。建造者模式的示意图和使用了建造者模式的CenterWidget建造方案如下图所示:

C++设计模式之建造者模式_第3张图片

建造者模式

使用建造者模式的`CenterWidget`方案

三、代码分析

下面给出CenterWidget方案的示例代码

#include <string>
#include <iostream>

using std::string;


class CenterWidget {
private:
    string Ui;
    string Animation;
    string Slot;
public:
    virtual ~CenterWidget (){};
    void setUi(const string& x){
        Ui = x;
    }

    void setAnimation(const string& x){
        Animation = x;
    }

    void setSlot(const string& x){
        Slot = x;
    }

    void show(){
        std::cout << "Ui = " << Ui  <<
        " Animation = " << Animation  <<
        " Slot = " << Slot << std::endl;
    }
};

class CenterWidgetBuilder {
public:
    virtual ~CenterWidgetBuilder(){}
    virtual void initUi() = 0;
    virtual void initAnimation() = 0;
    virtual void initSlot() = 0;
    virtual CenterWidget *getResult() = 0;
};

class ConcreteCenterWidgetBuilderA : public CenterWidgetBuilder {
private:
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderA():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderA(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Biu~Biu~Biu~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class ConcreteCenterWidgetBuilderB : public CenterWidgetBuilder {
private:
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderB():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderB(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Boom~Boom~Boom~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class Dirctor {
private:
    CenterWidgetBuilder *Builder;
public:
    Dirctor (CenterWidgetBuilder* builder):Builder(builder){};
    virtual ~Dirctor(){delete Builder;}
    void Construct(){
        Builder->initUi();
        Builder->initAnimation();
        Builder->initSlot();
    }
};

int main(void)
{
    ConcreteCenterWidgetBuilderA *builderA = new ConcreteCenterWidgetBuilderA;
    Dirctor *directorA = new Dirctor(builderA);
    directorA->Construct();
    builderA->getResult()->show();

    ConcreteCenterWidgetBuilderB *builderB = new ConcreteCenterWidgetBuilderB;
    Dirctor *directorB = new Dirctor(builderB);
    directorB->Construct();
    builderB->getResult()->show();
}
运行结果:
Ui = Q Ui  Animation = Biu~Biu~Biu~  Slot = connected to your heart
Ui = Q Ui  Animation = Boom~Boom~Boom~  Slot = connected to your heart

ConcreteCenterWidgetBuilderB和ConcreteCenterWidgetBuilderA中有重复代码,为了方便代码复用,我们可以使用ConcreteCenterWidgetBuilderB继承ConcreteCenterWidgetBuilderA,然后重写需要变化的initAnimation即可,代码修改如下:


class ConcreteCenterWidgetBuilderA : public CenterWidgetBuilder {
protected:  //为了子类能访问之,改为protected
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderA():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderA(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Biu~Biu~Biu~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class ConcreteCenterWidgetBuilderB : public ConcreteCenterWidgetBuilderA {
public:
    ConcreteCenterWidgetBuilderB(){}
    virtual ~ConcreteCenterWidgetBuilderB(){ };
    //只重写需要改变的部分
    virtual void initAnimation(){
        curWidget->setAnimation("Boom~Boom~Boom~");
    }
};

运行结果:
Ui = Q Ui  Animation = Biu~Biu~Biu~  Slot = connected to your heart
Ui = Q Ui  Animation = Boom~Boom~Boom~  Slot = connected to your heart

四、总结

建造者模式的主要优点 :

  • 将产品本身和产品的创建过程解耦,使得不同的创建过程创建出不同的实例
  • 可以很方便地增加新的建造者,实现新的产品实例的创建,符合开闭原则

建造者模式的主要缺点 :

  • 建造者模式只能创建具有许多共同点的产品,组成成分相似
  • 如果产品内部组成复杂多变,将需要定义大量的建造者类,使得系统复杂化

使用场景 :

  • 需要创建的产品具有多个组成部分且内部构造复杂
  • 需要指定产品的创建顺序
  • 对象创建的过程需要独立于该类

你可能感兴趣的:(设计模式,C++,linux,软件工程)