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

2023-10-23 15:46

本文主要是介绍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(){//构造Part1this->BuildPart1();for (int i = 0; i < 4; i++){//开四面窗户this->BuildPart2();}//构造判断某些变量bool flag=this->BuildPart3();//根据BuildPart3结果来判断是否BuildPart4if(flag){this->BuildPart4();}this->BuildPart5();}

现在的问题是每一个构建子步骤是变化的,因此将其实现为虚函数。整个构建的流程是稳定的,因此将其放到一个算法里面。整体代码如下:

class House{public:void Init(){//构造Part1this->BuildPart1();for (int i = 0; i < 4; i++){//开四面窗户this->BuildPart2();}//构造判断某些变量bool flag=this->BuildPart3();//根据BuildPart3结果来判断是否BuildPart4if(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(){//构造Part1this->BuildPart1(); //静态绑定for (int i = 0; i < 4; i++){//开四面窗户this->BuildPart2();}//构造判断某些变量bool flag=this->BuildPart3();//根据BuildPart3结果来判断是否BuildPart4if(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(){//构造Part1this->BuildPart1();for (int i = 0; i < 4; i++){//开四面窗户this->BuildPart2();}//构造判断某些变量bool flag=this->BuildPart3();//根据BuildPart3结果来判断是否BuildPart4if(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)

在这里插入图片描述

上图是《设计模式》GoF中定义的builder 构建器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
在这里插入图片描述

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

5. 要点总结

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

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

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

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

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

6. 其他参考

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

这篇关于C++设计模式_11_builder 构建器(小模式,不太常用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/268817

相关文章

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准