C++设计模式之建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
一、缘由
当我们在构造一个窗口控件的时候,往往包含三个方面的初始化工作:
- UI初始化
- 动画初始化
- 信号槽初始化
这样我们就可以构造好一个窗口控件了,我们可以看以下类图:
乍一看该实现并没有什么问题CenterWidget
类在其构造函数中调用了initUi
、initAnimation
、initSlot
三个私有成员函数分别进行Ui,动画,信号槽的初始化。可是当我们进行一个新的CenterWidget
构造时,要求动画效果改变,这时候需要怎么做呢?修改initAnimation
成员函数?作为一名优秀的程序员,提到修改类的时候就应该警惕,这个设计明显违反了开闭原则。这时候我们遇到到一个问题:构造的接口是固定的,构造的顺序是固定的,而要求构造的内容变化。
我们直觉上,这个问题的解就是:initUi
,initAnimation
,initSlot
三个成员函数必须为虚函数。在需要动画、Ui或者信号槽发生变化的时候,只要需要添加新的子类,重写其中某个函数需要变化即可。可是,当这三个成员函数都成为虚函数之后,就不可能在构造函数中调用,因为
在一个类构造期间,vtable还没有初始化完成,虚函数机制不会正确工作。
于是我们增加了一个Init
方法,在对象被构造出来之后调用之。此时类图如下:
对于这个类图来说,解决了开闭原则的问题,可是新的问题出现了,客户端需要调用Init
,有违反了接口隔离的和单一职责原则之嫌疑,为何这么说呢?
客户端的想法是:
- 我想要一个
CenterWidget
实例 - 然后将
CenterWidget
显示出来
而CenterWidget
提供的却是:
- 给你一个
CenterWidget
实例 - 然后调用
Init
接口初始化 - 然后将
CenterWidget
显示出来
对于客户端来说,它需要依赖一个它不需要的接口Init
,这个Init
不是客户端所要求的,反而更象是客户端需要CenterWidget
作为主窗口的逻辑功能而强行搭配的一个接口(注:接口隔离原则指的是客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。)。其次,作为主窗口类的CenterWidget
需要额外负责管理复杂的构造逻辑。这倒不是什么严重的问题,很多时候这种解决方法都是行之有效的,不过我们提出一种更为优雅的解决方法。
二、实现
为了解决上节所述的问题,我们将整个CenterWidget
构造职责抽取出来由单独的一个Builder
类承担。这样将initUi
、initAnimation
、initSlot
函数转移到Builder
类中。由Builder
类构造出来CenterWidget
实例,另外,有时候对initUi
、initAnimation
、initSlot
三个函数调用次序有所要求,引入一个Director
类专门管理对initUi
、initAnimation
、initSlot
的调用次序,指挥Builder
的工作,这就是建造者模式。建造者模式的示意图和使用了建造者模式的CenterWidget
建造方案如下图所示:
建造者模式
使用建造者模式的`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
四、总结
建造者模式的主要优点 :
- 将产品本身和产品的创建过程解耦,使得不同的创建过程创建出不同的实例
- 可以很方便地增加新的建造者,实现新的产品实例的创建,符合开闭原则
建造者模式的主要缺点 :
- 建造者模式只能创建具有许多共同点的产品,组成成分相似
- 如果产品内部组成复杂多变,将需要定义大量的建造者类,使得系统复杂化
使用场景 :
- 需要创建的产品具有多个组成部分且内部构造复杂
- 需要指定产品的创建顺序
- 对象创建的过程需要独立于该类