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

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

Java中使用 @Builder 注解的简单示例

《Java中使用@Builder注解的简单示例》@Builder简化构建但存在复杂性,需配合其他注解,导致可变性、抽象类型处理难题,链式编程非最佳实践,适合长期对象,避免与@Data混用,改用@G... 目录一、案例二、不足之处大多数同学使用 @Builder 无非就是为了链式编程,然而 @Builder

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、