C++设计模式_11_builder 构建器(小模式,不太常用)

builder 构建器也是属于“对象创建模式”模式的一种,是一个不常用,比较小的模式。

文章目录

  • 1. 动机(Motivation)
  • 2. 代码演示builder 构建器
    • 2.1 builder 构建器模式的形式1方法
    • 2.2 builder 构建器模式的形式2方法
    • 2.3 两种形式总结
  • 3. 模式定义
  • 4. 结构(Structure)
  • 5. 要点总结
  • 6. 其他参考

1. 动机(Motivation)

  • 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定

此处的描述与Template Method的描述相似,但是主要解决的是对象创建的问题

  • 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?

2. 代码演示builder 构建器

假设游戏中需要建房子,可能建茅草屋、砖瓦房、豪华房,但是建房子具有固定的几个流程,包括:地板、地基、窗户、房顶,但是不同房子的窗户、门等的构造方式可能不一样。

2.1 builder 构建器模式的形式1方法

假设构建窗户、门等是几个步骤

	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了。

2.2 builder 构建器模式的形式2方法

但是做到此处仍有优化的空间,某些情况下对象过于复杂,除了上面的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指针了,这样演化已经够了。

2.3 两种形式总结

  • 两种形式均是属于builder构建器模式;
  • 根据类的复杂程度决定使用形式1或者形式2,简单的情况下使用形式1,复杂的情况下使用形式2

3. 模式定义

将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
——《设计模式》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(); 
    }

4. 结构(Structure)

C++设计模式_11_builder 构建器(小模式,不太常用)_第1张图片

上图是《设计模式》GoF中定义的builder 构建器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
C++设计模式_11_builder 构建器(小模式,不太常用)_第2张图片

这只是一种演化的形式,其实Director和Builder像最初代码中合并的形式也是可以的,主要看类的复杂度,重构原则上是类类复杂就拆拆拆,类简单就是合并合并

5. 要点总结

  • Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。

房子整体流程稳定,房子的各个部分窗户等是变化的

  • 变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。

  • 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#) 。

C++中不能直接调用虚函数,这也是将Builder移出去的部分原因,但是在C#,java是可以的

6. 其他参考

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

你可能感兴趣的:(#,C++设计模式,c++,设计模式)