builder 构建器也是属于“对象创建模式”模式的一种,是一个不常用,比较小的模式。
“一个复杂对象”
的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化
,但是将它们组合在一起的算法却相对稳定
。此处的描述与Template Method的描述相似,但是主要解决的是对象创建的问题
假设游戏中需要建房子,可能建茅草屋、砖瓦房、豪华房,但是建房子具有固定的几个流程,包括:地板、地基、窗户、房顶,但是不同房子的窗户、门等的构造方式可能不一样。
假设构建窗户、门等是几个步骤
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
构造房子的固定流程如下:
void Init()
{
//构造Part1
this->BuildPart1();
for (int i = 0; i < 4; i++){
//开四面窗户
this->BuildPart2();
}
//构造判断某些变量
bool flag=this->BuildPart3();
//根据BuildPart3结果来判断是否BuildPart4
if(flag){
this->BuildPart4();
}
this->BuildPart5();
}
现在的问题是每一个构建子步骤是变化的,因此将其实现为虚函数。整个构建的流程是稳定的,因此将其放到一个算法里面。整体代码如下:
class House{
public:
void Init()
{
//构造Part1
this->BuildPart1();
for (int i = 0; i < 4; i++){
//开四面窗户
this->BuildPart2();
}
//构造判断某些变量
bool flag=this->BuildPart3();
//根据BuildPart3结果来判断是否BuildPart4
if(flag){
this->BuildPart4();
}
this->BuildPart5();
}
virtual ~House(){}
protected:
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
这样写下来就会发现其整个流程真的很像Template Method模板方法。
那么首先有一个问题,既然是构建一个对象,是否可以写为构造函数呢?得到如下代码:
class House{
public:
House()
{
//构造Part1
this->BuildPart1(); //静态绑定
for (int i = 0; i < 4; i++){
//开四面窗户
this->BuildPart2();
}
//构造判断某些变量
bool flag=this->BuildPart3();
//根据BuildPart3结果来判断是否BuildPart4
if(flag){
this->BuildPart4();
}
this->BuildPart5();
}
virtual ~House(){}
protected:
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
答案是不能的,这是因为在C++中比较特殊,在构造函数中调用虚函数的话,它完全是静态绑定,而不是动态绑定,举例来说:this->BuildPart1();
应该调用virtual void BuildPart1()=0;
的实现,但此处没实现,所以会报错的。
在构造函数中,虚函数是不可以调用子类的虚函数override的版本,这是因为子类的构造函数是先调用父类的构造函数,如果允许this->BuildPart1();
动态绑定的话,子类的构造函数需要先调用House
的构造函数,House
的构造函数再去调用子类的override的版本的话,就会在子类的构造函数还没完成,子类的虚函数先被调用,这就违背对象的基本伦理,得子类先生下来,才能行使行为
。在其他语言中可以实现动态绑定。
假设构建石头房子,得到如下:
class House{
public:
void Init()
{
//构造Part1
this->BuildPart1();
for (int i = 0; i < 4; i++){
//开四面窗户
this->BuildPart2();
}
//构造判断某些变量
bool flag=this->BuildPart3();
//根据BuildPart3结果来判断是否BuildPart4
if(flag){
this->BuildPart4();
}
this->BuildPart5();
}
virtual ~House(){}
protected:
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
//构建石头房子
class StoneHouse: public House{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
int main ()
{
House* pHouse = new StoneHouseBuilder;
pHouse->Init();
}
当然如果需要构建茅草房等也是类似的,按理来说Builder模式,写到此时已经是OK了。
但是做到此处仍有优化的空间,某些情况下对象过于复杂,除了上面的Init(),还要实现其他字段,如果搅在一起会很麻烦,需要进行拆分。马丁福乐重构理论中讲到:不能有太肥的类,类的行为代码太多就不太好,构建过程如此复杂,需要将其提取出来,变成一个单独的类的行为,一般会将类进行拆分,一部分是本身类的状态和行为,另一部分是专门做构建的。此例中将House类中的Init()拆分为一个单独的类。
class House{
//....
};
class HouseBuilder {
public:
House* GetResult(){
return pHouse;
}
virtual ~HouseBuilder(){}
protected:
House* pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
//稳定的,重写的时候只需要重写此类
class HouseDirector{
public:
HouseBuilder* pHouseBuilder;
HouseDirector(HouseBuilder* pHouseBuilder){
this->pHouseBuilder=pHouseBuilder;
}
House* Construct(){
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag=pHouseBuilder->BuildPart3();
if(flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
};
就是上面的方式,使得构建的过程会发现,将House和HouseBuilder相分离,这样之后,具体再去实现的时候可以有一个GetResult(),外接就能拿到pHouse指针了,这样演化已经够了。
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
——《设计模式》GoF
如果只是做到最初的版本已经够了,最后复杂的版本是考虑将一个复杂对象的构建与其表示相分离
,House是表示,HouseBuilder是构建。同样的构建过程为:
House* Construct(){
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag=pHouseBuilder->BuildPart3();
if(flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
上图是《设计模式》GoF中定义的builder 构建器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
这只是一种演化的形式,其实Director和Builder像最初代码中合并的形式也是可以的,主要看类的复杂度,重构原则上是类类复杂就拆拆拆,类简单就是合并合并
房子整体流程稳定,房子的各个部分窗户等是变化的
变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#) 。
C++中不能直接调用虚函数,这也是将Builder移出去的部分原因,但是在C#,java是可以的
C++设计模式——建造者模式