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

news2024/10/7 6:40:50

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)

在这里插入图片描述

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

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

5. 要点总结

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

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

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

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

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

6. 其他参考

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1124012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

推荐一款最近风很大的配音工具~

逐渐发现身边越来越多人开始朝着丰富多彩的副业方向发展&#xff0c;而在这其中&#xff0c;“自媒体”深受大家喜爱&#xff0c;许多人开始看到了商机并纷纷“下海”制作短视频&#xff0c;那么不知道在座的各位“下海”了没有呢哈哈哈哈&#xff1f;短视频需求越来越大&#…

测试左移和右移怎么做,这篇文章写的太详细了

通俗的讲&#xff1a;左移是往开发阶段移&#xff0c;右移是往发布之后移。 正常测试&#xff1a;提测后的测试工作——到——发布验证完成阶段。 测试左移&#xff1a;提测之前的测试。 如&#xff1a;代码单元测试&#xff0c;代码质量检测&#xff0c;代码接口持续测试 等。…

为什么电商使用高匿代理ip更有效果?

前言 随着电商业务的不断发展&#xff0c;越来越多的电商平台开始使用代理IP作为一种有效的反爬虫手段。而高匿代理IP作为代理IP中的一种&#xff0c;其相较于其他类型的代理IP可以更好地保护电商业务的隐私和安全&#xff0c;从而更加有效地为电商平台服务。本文将从代理IP的…

苹果Mac电脑音频处理工具:iZotope RX 10 最新 for mac

iZotope RX 10是一款知名的音频修复和后期处理工具。它因其出色的音频修复功能和强大的音频编辑工具而被广泛应用于音乐制作、电影后期制作、广播电视等领域。 以下是iZotope RX 10的一些主要功能和特点&#xff1a; 音频修复&#xff1a;iZotope RX 10具备强大的音频修复功能…

北邮22级信通院数电:Verilog-FPGA(6)第六周实验:全加器

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 先抄作业&#xff01;&#xff01;&#xff01;&am…

机器人系统 ROS 常用命令行工具

1. 启动ros 主节点 roscore roscore运行成功如图&#xff1a; 1.1 rosrun 启动服务节点 例子&#xff1a;启动一个小乌龟节点 rosrun turtlesim turtlesim_node运行结果如图&#xff1a; 1.2 启动键盘控制 打开新的命令窗口&#xff0c;启动turtle_teleop_key 节点 rosr…

三步搞定查分系统

成绩查询系统&#xff0c;简单来说&#xff0c;就是一个让学生能够自助查询成绩的系统。无论是在学校、培训机构&#xff0c;还是在家庭教育中&#xff0c;成绩查询都是一个重要的环节。那么如何建立一个简单实用的成绩查询系统呢&#xff1f;下面就为你揭秘三步打造查分系统的…

ubuntu1804服务器设置静态IP

修改/etc/netplan/00-install-config.yaml文件中的内容 查看网关命令是route -n

【广州华锐互动】关于物理力学的3D实验实操平台

在科学的广阔领域中&#xff0c;物理力学是一个至关重要的分支&#xff0c;它探索了物体在力作用下的运动规律。然而&#xff0c;传统的物理实验往往需要复杂的设备和大量的操作&#xff0c;这对于学生来说是一项巨大的挑战。为了解决这个问题&#xff0c;广州华锐互动开发了物…

GoLong的学习之路(二)语法之基本数据类型

书接上回&#xff1a;我在GoLong的学习之路&#xff08;一&#xff09;中在常量最后说了iota的作用。今天这里我在介绍一下我学习Go语言中基本数据类型。 文章目录 Go中的基本数据类型整型特殊整型数字字面语法 浮点型复数布尔值字符串字符串转义符多行字符字符串的常用操作&am…

小程序 swiper滑动

整个红色区域为可滑动区域&#xff0c;数字1区域为展示区域&#xff0c;数字2为下一个展示模块 <scroll-view class"h_scroll_horizontal" enhanced"ture" bind:touchend"touchEnd" bind:touchstart"touchStart"><view clas…

【iOS】——知乎日报第一周总结

文章目录 一、框架和布局问题二、线程冲突问题三、下拉刷新问题四、添加网络请求的图片五、时间标题设置问题 一、框架和布局问题 仿写知乎日报用到的框架依旧是MVC框架&#xff0c;所以一开始要想好该怎么搭建大体框架&#xff0c;对于各个模块该怎么分配&#xff0c;需要用到…

java--自增自减运算符

1.自增自减运算符 注意&#xff1a;、--只能操作变量&#xff0c;不能操作字面量的。 2.自增自减的使用注意事项 1.、--如果不是单独使用(如果在表达式中、或者同时有其它操作)&#xff0c;放在变量前后会存在明显区别 1.1放在变量前面&#xff0c;先对变量进行1、-1&#xff…

关于unicode视觉、存储长度的事儿

问题&#xff1a;unicode 找不到合适的正则 字符串视觉长度 使用lodash&#xff1b; import toarray from lodash.toarray; toarray(str);unicode视觉长度 在字符串视觉的基础上判断每个元素的length是否大于1来累积&#xff1b; 字节长度 Array.from(str).length 问题举…

Java数据结构之稀疏数组

目录 线性结构与非线性结构线性结构非线性结构 稀疏数组应用场景 代码实现二维数组转稀疏数组稀疏数组转二维数组 线性结构与非线性结构 线性结构 数据结构分两种&#xff0c;线性与非线性&#xff0c;线性结构的数据元素之间存在一对一的关系。 一对一指的是每个数据元素都…

淘宝app商品详情源数据API接口(解决滑块问题)可高并发采集

通过API接口采集淘宝商品列表和app商品详情遇到滑块验证码的解决方法&#xff08;带SKU和商品描述&#xff0c;支持高并发&#xff09;&#xff0c;主要是解决了高频情况下的阿里系滑块和必须要N多小号才能解决的反扒问题&#xff0c;以后都可以使用本方法&#xff1a; 大家都…

Qt+树莓派4B窗口半透明效果实现

文章目录 前言一、窗口半透明&#xff0c;窗口部件不透明1、构造函数中的设置2、paintEvent3、效果4、树莓派4B配置5、最终效果 前言 在树莓派4B下&#xff0c;使用Qt开发窗口半透明而窗口部件不透明效果时,发现窗口没能正常实现半透明效果,而是显示纯黑色背景。同样的代码在wi…

Windows 95 的辉煌诞生历史

1992 年 2 月&#xff0c;Windows 3.1 的研发即将结束&#xff0c;而 Windows 团队正忙得不亦乐乎地计划他们的下一盘大棋。到了 3 月 5 日&#xff0c;他们终于悠哉悠哉地敲定了战略大计&#xff1a;横扫桌面、笔记本、移动设备以及时髦的触控笔设备。至于那些高大上的服务器和…

基于springboot+vue网上书城系统53

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

Qt使用一行代码轻松改变按钮图标颜色

Qt使用一行代码轻松改变QPushButton QToolButton图标颜色 需求&#xff1a;Qt程序主界面改变主题颜色时&#xff0c;例如白色背景色切换为深色模式&#xff0c;背景会变成深色。通常按键的图标会使用黑色&#xff0c;这时应该将图标改为白色系&#xff0c;应该轻松快捷去实现&…