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

news2024/10/7 10:24:59

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.coloradmin.cn/o/1577571.html

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

相关文章

Discord注册教程:Discord刚注册就被封怎么办?附申诉教程!

Discord如今在海外社交媒体平台中迅速崛起&#xff0c;许多社交媒体营销人员也纷纷利用其社群特性进行推广&#xff0c;Discord注册也就成为社媒营销人员必经之路。然而&#xff0c;很多人注册Discord账号时常常会想&#xff1a;“在国内使用Discord会封号吗&#xff1f;”事实…

订阅edk2社区邮件列表

给社区发邮件步骤 UEFI订阅邮件列表 开发者订阅邮箱 develedk2.groups.io | Home 点击Join This Group&#xff0c;按照步骤填写自己邮箱地址&#xff08;该地址是edk2,发送邮件到该邮箱的地址&#xff09; 自己邮箱确认就可以自动收到邮件了 比如&#xff1a;

【深度学习】最强算法之:深度Q网络(DQN)

深度Q网络 1、引言2、深度Q网络2.1 定义2.2 原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 马上清明小长假了&#xff0c; 你这准备去哪里玩啊&#xff1f; 小鱼&#xff1a;哪也不去&#xff0c;在家待着 小屌丝&#xff1a…

如何让阿里云AI001号员工帮我写代码(含IDEA插件使用)

国内首个AI程序员入职阿里云&#xff1a;专属工号AI001&#xff0c;KPI是一人写完公司20%代码。 不管是真是假&#xff0c;AI 程序员发展的趋势是无法改变的&#xff0c;小米汽车发布会上&#xff0c;雷军说到小米汽车工厂的自动化率达到90%以上&#xff0c;有些车间甚至100%的…

基于javassmJSP的家用电器销售网站

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

面试字节被挂了

分享一个面试字节的经历。 1、面试过程 一面&#xff1a;上来就直接"做个题吧"&#xff0c;做完之后&#xff0c;对着简历上一个项目聊&#xff0c;一直聊到最后&#xff0c;还算比较正常。 二面&#xff1a;做自我介绍&#xff0c;花几分钟聊了一个项目&#xff…

江南大学酒科技馆OLED透明屏项目方案

一、项目概述 本项目旨在为无锡江南大学酒科技馆提供OLED透明屏解决方案&#xff0c;通过安装2x2的OLED透明屏&#xff0c;为参观者带来全新的视觉体验&#xff0c;同时提升酒科技馆的展示效果与科技感。 二、产品选型 本项目选用OLED透明屏&#xff0c;其具有高透明度、高对比…

Windows/Jerry

Jerry Enumeration nmap 扫描系统发现对外开放了 8080 端口&#xff0c;再次使用 nmap 扫描端口详细信息&#xff0c;发现运行着 Apache Tomcat ┌──(kali㉿kali)-[~/vegetable/HTB/Jerry] └─$ nmap -sV -sC -p 8080 -oA nmap 10.10.10.95 -Pn Starting Nmap 7.93 ( htt…

【分治算法】Strassen矩阵乘法Python实现

文章目录 [toc]问题描述基础算法时间复杂性 Strassen算法时间复杂性 问题时间复杂性Python实现 个人主页&#xff1a;丷从心. 系列专栏&#xff1a;Python基础 学习指南&#xff1a;Python学习指南 问题描述 设 A A A和 B B B是两个 n n n \times n nn矩阵&#xff0c; A A…

东方博宜 1426. 年龄与疾病

东方博宜 1426. 年龄与疾病 思路&#xff1a;1 读取数组 2 遍历数组并进行比较 遇到的坑是百分号且保留两位的输出方式&#xff0c;以及两个整数求商的时候要记得转换成小数形式 #include<iostream> #include<cstdio> using namespace std; int main() {int n ;cin…

第十四届蓝桥杯岛屿个数

题目描述&#xff1a; 小蓝得到了一副大小为 MN 的格子地图&#xff0c;可以将其视作一个只包含字符 0&#xff08;代表海水&#xff09;和 1&#xff08;代表陆地&#xff09;的二维数组&#xff0c;地图之外可以视作全部是海水&#xff0c;每个岛屿由在上/下/左/右四个方向上…

晶核养号攻略:如何轻松搬砖?两大要点!

晶核游戏中&#xff0c;想通过搬砖来养号并不是一件难事。本攻略将为你介绍两种主要的金币获取方式&#xff0c;让你轻松提升游戏财富&#xff0c;实现更多游戏目标。 一、刷深渊&#xff1a;稳定金币收入 深渊地图在晶核游戏中是一个稳定的金币来源。这张地图从55级开始可刷&…

【Segment Anything Model】十三:Meta的最新工作EfficientSAM,微调到自己的数据集,代码。

&#x1f349; 博主微信 cvxiayixiao 还有其他专栏点击头像查询 &#x1f353; 【Segment Anything Model】计算机视觉检测分割任务专栏。 &#x1f351; 【公开数据集预处理】特别是医疗公开数据集的接受和预处理&#xff0c;提供代码讲解。 &#x1f348; 【opencv图像处理】…

N4433A安捷伦N4433A电子校准件

181/2461/8938产品概述&#xff1a; 300 kHz至20 GHz频率范围标准3.5毫米接口通过单一连接实现快速完整的3或4端口校准NIST可追溯的精确校准减少连接器磨损用于直接控制PNA和ENA系列网络分析仪的USB接口可靠的固态开关提供混合3.5毫米公/母连接器选项 安捷伦N4433A微波电子校准…

代码随想录|Day34|动态规划03|343.整数拆分、96.不同的二叉搜索树

343.整数拆分 动规五步&#xff1a; 确定 dp[i] 含义&#xff1a;拆分数字 i&#xff0c;可以获得的最大乘积为 dp[i]。递推公式&#xff1a;dp[i] max(j * (i - j), j * dp[i - j])。i 可以被拆解为两个数&#xff08;j 和 i - j&#xff09;或者多个数&#xff08;j 和 dp[i…

app上架-您的应用存在最近任务列表隐藏风险活动的行为,不符合华为应用市场审核标准。

上架提示 您的应用存在最近任务列表隐藏风险活动的行为&#xff0c;不符合华为应用市场审核标准。 修改建议&#xff1a;请参考测试结果进行修改。 请参考《审核指南》第2.19相关审核要求&#xff1a;https://developer.huawei.com/consumer/cn/doc/app/50104-02 造成原因 …

数字电路基础(Digital Circuit Basis )

目录 一、什么是数字电路&#xff1f; &#xff08;Digital Circuit &#xff09; 1.概念 2.分类 3.优点 4.数电与模电的区别 二、数制 (十进制&#xff1a;Decimal) 1.概述 2.进位制 3.基数 4.位权 5.二进制的算术运算 三、编码 (二进制&#xff1a;Binary ) 1.什…

2024/4/1—力扣—按摩师

代码实现&#xff1a; 思路&#xff1a;打家劫舍题 int massage(int *nums, int numsSize) {if (nums NULL || numsSize 0) {return 0;}if (numsSize 1) {return nums[0];}int dp[numsSize];memset(dp, 0, sizeof(dp));dp[0] nums[0];dp[1] (nums[0] < nums[1] ? nums…

【大功率汽车大灯升压方案】LED恒流驱动芯片FP7208升压车灯调光应用,PWM内部转模拟,调光深度1%,无频闪顾虑,低亮无抖动

宝马X5前中排座椅宽大舒适&#xff0c;车厢内储物空间丰富。操控性能极佳&#xff0c;底盘稳扎精良。原车为氙气灯&#xff0c;其实宝马的氙气大灯配的比其他车型要好&#xff0c;照明效果是没得说的。但是不管什么灯久了都会出现光衰的情况。下面这辆宝马X5车灯已老化严重。 宝…

【Linux】安装+基本指令

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12625432.html 目录 Linux系统的安装 登录 XShell 下的复制粘贴 指令 pwd指令 ls指令 cd 指令 …