创建型模式--5.建造者模式【卡雷拉公司】

1. 造船,我是专业的

在海贼世界中,水之都拥有全世界最好的造船技术,三大古代兵器之一的冥王就是由岛上的造船技师们制造出来的。现在岛上最大、最优秀的造船公司就是卡雷拉公司,它的老板还是水之都的市长,财富权力他都有,妥妥的人生赢家。
在这里插入图片描述

众所周知,在冰山身边潜伏着很多卧底,他们都是世界政府直属秘密谍报机关 CP9成员,目的是要得到古代兵器冥王的设计图,但是很不幸图纸后来被弗兰奇烧掉了。既然他们造船这么厉害,我也来到了卡雷拉公司,学习一下他们是怎么造船的。

1.1 桑尼号

以下是我拿到的冰山和弗兰奇给路飞造的桑尼号的部分设计图纸:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过图纸可以感受到造一艘船的工序是极其复杂的,看到这儿,曾经作为程序猿的我又想到了写程序,如果只通过一个类直接把这种结构的船构建出来完全是不可能,先不说别的光构造函数的参数就已经不计其数了。

冰山给出的解决方案是化繁为简,逐个击破。也就是分步骤创建复杂的对象,并且允许使用相同的代码生成不同类型和形式的对象,他说这种模式叫做生成器模式(也叫建造者模式)

1.2 生成器

生成器模式建议将造船工序的代码从产品类中抽取出来, 并将其放在一个名为生成器的独立类中。
在这里插入图片描述

在这个生成器类中有一系列的构建步骤,每次造船的时候,只需从中选择需要的步骤并调用就可以得到满足需求的实例对象。

假设我们要通过上面的生成器建造很多不同规格、型号的海贼船,那么就需要创建多个生成器,但是有一点是不变的:生成器内部的构建步骤不变

简约型标准型豪华型
船体
动力
武器
内室毛坯毛坯精装

比如,我想建造两种型号的海贼船:桑尼号梅利号,并且按照上面的三个规格,每种造一艘,此时就需要两个生成器:桑尼号生成器梅利号生成器,并且这两个生成器还需要对应一个父类,父类生成器中的建造函数应该设置为虚函数

1.3 主管

冰山说可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。

在这里插入图片描述

严格来说, 程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。

此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个生成器与主管类关联, 然后使用主管类来构造产品, 就能从生成器处获得构造结果了。

2. 说干就干

2.1 船模型

现在我们开始着手把路飞的海贼船桑尼号梅利号使用生成器模式键造出来。

  • 一共需要三个生成器类,一共父类,两个子类
  • 父类可以是一个抽象类,提供的建造函数都是虚函数
  • 在两个生成器子类中,使用建造函数分别将桑尼号梅利号各个零部件造出来。

如果我们仔细分析,发现还需要解决另外一个问题,通过生成器得到了海贼船的各个零部件,这些零部件必须有一个载体,那就是海贼船对象。因此,还需要提供一个或多个海贼船类。

因为桑尼号梅利号这两艘的差别非常之巨大,所以我们定义两个海贼船类,代码如下:

// 桑尼号
class SunnyShip
{
public:// 添加零件void addParts(string name){m_parts.push_back(name);}void showParts(){for (const auto& item : m_parts){cout << item << "   ";}cout << endl;}
private:vector<string> m_parts;
};// 梅利号
class MerryShip
{
public:// 组装void assemble(string name, string parts){m_patrs.insert(make_pair(name, parts));}void showParts(){for (const auto& item : m_patrs){cout << item.first << ": " << item.second << "  ";}cout << endl;}
private:map<string, string> m_patrs;
};

在上面的两个类中,通过一个字符串来代表某个零部件,为了使这两个类有区别SunnyShip 类中使用vector 容器存储数据,MerryShip 类中使用map 容器存储数据。

2.2 船生成器

虽然有海贼船类,但是这两个海贼船类并不造船,每艘船的零部件都是由他们对应的生成器类构建完成的,下面是生成器类的代码:

抽象生成器

// 生成器类
class ShipBuilder
{
public:virtual void reset() = 0;virtual void buildBody() = 0;virtual void buildWeapon() = 0;virtual void buildEngine() = 0;virtual void buildInterior() = 0;virtual ~ShipBuilder() {}
};

在这个抽象类中定义了建造海贼船所有零部件的方法,在这个类的子类中需要重写这些虚函数,分别完成桑尼号梅利号零件的建造。

桑尼号生成器

// 桑尼号生成器
class SunnyBuilder : public ShipBuilder
{
public:SunnyBuilder(){reset();}~SunnyBuilder(){if (m_sunny != nullptr){delete m_sunny;}}// 提供重置函数, 目的是能够使用生成器对象生成多个产品void reset() override{m_sunny = new SunnyShip;}void buildBody() override{m_sunny->addParts("神树亚当的树干");}void buildWeapon() override{m_sunny->addParts("狮吼炮");}void buildEngine() override{m_sunny->addParts("可乐驱动");}void buildInterior() override{m_sunny->addParts("豪华内室精装");}SunnyShip* getSunny(){SunnyShip* ship = m_sunny;m_sunny = nullptr;return ship;}
private:SunnyShip* m_sunny = nullptr;
};

在这个生成器类中只要调用build 方法,对应的零件就会被加载到SunnyShip 类的对象 m_sunny 中,当船被造好之后就可以通过SunnyShip* getSunny()方法得到桑尼号的实例对象,当这个对象地址被外部指针接管之后,当前生成器类就不会再维护其内存的释放了。如果想通过生成器对象建造第二艘桑尼号就可以调用这个类的reset()方法,这样就得到了一个新的桑尼号对象,之后再调用相应的建造函数,这个对象就被初始化了。

梅利号生成器

// 梅利号生成器
class MerryBuilder : public ShipBuilder
{
public:MerryBuilder(){reset();}~MerryBuilder(){if (m_merry != nullptr){delete m_merry;}}void reset() override{m_merry = new MerryShip;}void buildBody() override{m_merry->assemble("船体", "优质木材");}void buildWeapon() override{m_merry->assemble("武器", "四门大炮");}void buildEngine() override{m_merry->assemble("动力", "蒸汽机");}void buildInterior() override{m_merry->assemble("内室", "精装");}MerryShip* getMerry(){MerryShip* ship = m_merry;m_merry = nullptr;return ship;}
private:MerryShip* m_merry = nullptr;
};

梅利号的生成器和桑尼号的生成器内部做的事情是一样的,在此就不过多赘述了。

2.3 包工头

如果想要隐藏造船细节,就可以添加一个主管类,这个主管类就相当于一个包工头,脏活累活他都干了,我们看到的就是一个结果。

根据需求,桑尼号和梅利号分别有三个规格,简约型、标准型、豪华型,根据不同的规格,有选择的调用生成器中不同的建造函数,就可以得到最终的成品了。

// 主管类
class Director
{
public:void setBuilder(ShipBuilder* builder){m_builder = builder;}// 简约型void builderSimpleShip(){m_builder->buildBody();m_builder->buildEngine();}// 标准型void builderStandardShip(){builderSimpleShip();m_builder->buildWeapon();}// 豪华型void builderRegalShip(){builderStandardShip();m_builder->buildInterior();}
private:ShipBuilder* m_builder = nullptr;
};

在使用主管类的时候,需要通过setBuilder(ShipBuilder* builder)给它的对象传递一个生成器对象,形参是父类指针,实参应该是子类对象,这样做的目的是为了实现多态,并且在这个地方这个函数是一个传入传出参数

3. 验收

最后测试一个桑尼号和梅利号分别对应的三种规格的船能否被建造出来:

// 建造桑尼号
void builderSunny()
{Director* director = new Director;SunnyBuilder* builder = new SunnyBuilder;// 简约型director->setBuilder(builder);director->builderSimpleShip();SunnyShip* sunny = builder->getSunny();sunny->showParts();delete sunny;// 标准型builder->reset();director->setBuilder(builder);director->builderStandardShip();sunny = builder->getSunny();sunny->showParts();delete sunny;// 豪华型builder->reset();director->setBuilder(builder);director->builderRegalShip();sunny = builder->getSunny();sunny->showParts();delete sunny;delete builder;delete director;
}// 建造梅利号
void builderMerry()
{Director* director = new Director;MerryBuilder* builder = new MerryBuilder;// 简约型director->setBuilder(builder);director->builderSimpleShip();MerryShip* merry = builder->getMerry();merry->showParts();delete merry;// 标准型builder->reset();director->setBuilder(builder);director->builderStandardShip();merry = builder->getMerry();merry->showParts();delete merry;// 豪华型builder->reset();director->setBuilder(builder);director->builderRegalShip();merry = builder->getMerry();merry->showParts();delete merry;delete builder;delete director;
}int main()
{builderSunny();cout << "=====================================" << endl;builderMerry();
}

程序输出:

神树亚当的树干   可乐驱动
神树亚当的树干   可乐驱动   狮吼炮
神树亚当的树干   可乐驱动   狮吼炮   豪华内室精装
===================================== 
船体: 优质木材  动力: 蒸汽机
船体: 优质木材  动力: 蒸汽机  武器: 四门大炮
船体: 优质木材  动力: 蒸汽机  内室: 精装  武器: 四门大炮

可以看到,输出结果是没问题的,使用生成器模式造船成功!

4. 收工

最后根据上面的代码把UML类图画一下(在学习设计模式的时候只能最后出图,在做项目的时候应该是先画UML类图,再写程序)。
在这里插入图片描述

通过编写的代码可得知Director 类ShipBuilder 类之间有两种关系依赖关联,但在描述这二者的关系的时候只能画一条线,一般会选择最紧密的那个关系,在此处就是关联关系

在这个图中,没有把使用这用这些类的客户端画出来,这个客户端对应的是上面程序中的main()函数中调用的测试代码,在真实场景中对应的应该是一个客户端操作界面,由用户做出选择,从而在程序中根据选择建造不同型号,不同规格的海贼船。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang 开发实战day09 - package Scope

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程09 - package Sc…

5个最佳的免费AI图像生成器

原文地址&#xff1a;https://readwrite.com/ai-5-of-the-best-free-ai-image-generators/ Ideogram: Social AI image generator, limited daily prompts, impressive quality.NightCafe: Creative options, custom model training, limited free daily credits.Runway AI: A…

三流大学毕业,物流专业转行自述:“从月薪4K到现在月入2W+,我做到了哪些?”

我是25岁转行学python的。说实在&#xff0c;转行就是奔着挣钱去的。希望我的经历可以给想转行的朋友带来一点启发和借鉴。 先简单介绍下个人背景&#xff0c;三流大学毕业&#xff0c;物流专业&#xff0c;学习能力一般&#xff0c;没啥特别技能&#xff0c;反正就很普通的一…

【C++进阶】哈希的应用之位图和布隆过滤器

位图和布隆过滤器 一&#xff0c;位图1. 实现2. 位图的应用 二&#xff0c;布隆过滤器1. 使用场景2. 模拟实现 三&#xff0c;海量数据面试题哈希切分 四&#xff0c;总结 这一节我们来看哈希的应用 一&#xff0c;位图 先来看一个面试题 这里如果用unordered_set来解决&…

多叉树题目:子树中标签相同的结点数

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;子树中标签相同的结点数 出处&#xff1a;1519. 子树中标签相同的结点数 难度 5 级 题目描述 要求 给你一个树&#xff08;即一个连通的无向无环图…

MySQL高级篇(B-Tree、Btree)

目录 1、Btree&#xff08;B-Tree&#xff09; 1.1、B-Trees的特点 二叉树缺点&#xff1a;顺序插入时&#xff0c;会形成一个链表&#xff0c;查询性能大大降低。大数据量情况下&#xff0c;层级较深&#xff0c;检索速度慢。红黑树&#xff1a;大数据量情况下&#xff0c;层…

10款白嫖党必备的ai写作神器,你都知道吗? #媒体#人工智能#其他

从事自媒体运营光靠自己手动操作效率是非常低的&#xff0c;想要提高运营效率就必须要学会合理的使用一些辅助工具。下面小编就跟大家分享一些自媒体常用的辅助工具&#xff0c;觉得有用的朋友可以收藏分享。 1.飞鸟写作 这是一个微信公众号 面向专业写作领域的ai写作工具&am…

rsync+inotify实时同步 和 GFS分布式文件系统概述

目录 一、rsyncinotify实时同步 1.1.实时同步的优点 1.2.Linux内核的inotify机制 1.3.发起端配置rsyncInotify 1.4.配置远程登陆 1.4.1.修改rsync源服务器配置192.168.190.101 ​编辑 1.4.2.配置server 192.168.190.102 二、GFS 2.1.GlusterFS简介 2.2.GlusterFS特点…

python代码使用过程中使用快捷键注释时报错

1.代码 2.代码报错 3.代码注释后的结果 4. 原因

我去,PMP原来不是所有人都能报!

很多人可能觉得PMP的报名条件很复杂&#xff0c;又是经验要求&#xff0c;又是学历要求的&#xff0c;网络上关于PMP报名条件说的层出不穷&#xff0c;今天给大家统一一下&#xff0c;报名PMP究竟需要什么条件&#xff1a; 官方报考条件&#xff1a; 一、报名考生必须具备35小…

动态输出n位小数——满满都是坑!

【题目描述】 输入正整数a&#xff0c;b&#xff0c;c&#xff0c;输出a/b的小数形式&#xff0c;精确到小数点后c位。a,b ≤10^6 &#xff0c;c≤100。输入包含多组数据&#xff0c;结束标记为a&#xff1d;b&#xff1d;c&#xff1d;0。 【样例输入】 1 6 4 0 0 0 【样…

Windows:IntelliJ IDEA Ultimate 安装 PHP 插件

在 IntelliJ IDEA Ultimate 中安装 PHP 插件&#xff0c;支持PHP开发调试 首先&#xff0c;进入File > Setting&#xff1a; 再次选择Plugins&#xff0c;然后选择上面的 Marketplace。 在搜索栏中输入 PHP&#xff0c;然后单击左侧的 Install 进行安装就可以了。 安装成功…