创建型模式--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()函数中调用的测试代码,在真实场景中对应的应该是一个客户端操作界面,由用户做出选择,从而在程序中根据选择建造不同型号,不同规格的海贼船。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大话设计模式——11.桥接模式(Bridge Pattern)

简介 将抽象部分与它的实现部分分离&#xff0c;使它们可以独立变化。 UML图&#xff1a; 应用场景&#xff1a; 系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性不想使用继承导致系统类的个数急剧增加某个类存在多个变化维度使用继承方式容易出现类的膨胀 示例…

护眼台灯什么牌子好?爱德华、书客、明基护眼台灯真实测评

台灯是我们日常比较实用的一盏桌面照明灯具&#xff0c;不管是休闲看书、学习、办公都会用得上。如果使用了一款光源不好的台灯&#xff0c;时间长了可能就会影响我们的眼睛健康&#xff0c;特别是孩子的眼睛&#xff0c;还没有发育完全&#xff0c;影响更大。 在面临如今市面上…

设计模式——2_7 状态(State)

欲买桂花同载酒&#xff0c;终不似&#xff0c;少年游 ——刘过《唐多令芦叶满汀州》 文章目录 定义图纸一个例子&#xff1a;如何模拟一个转笔刀自动转笔刀PencilPencilSharpener 投诉和改善钝刀BladePencilSharpener 没有铅笔PencilSharpener if if ifStatePencilSharpener 碎…

动态物体检测 | 复杂环境下多目标动态物体实时检测算法实现

项目应用场景 面向复杂环境下的多目标动态物体实时检测场景&#xff0c;项目采用一种在线体积映射的算法实现。 项目效果 项目细节 > 具体参见项目 README.md (1) 安装、编译工程&#xff0c;包括 ROS&#xff0c;具体步骤参见 README(2) 执行 DOALS Sequence roslaunch d…

Point cloud转 Laser sacn

文章目录 概要安装pointcloud_to_laserscan修改配置运行结果 概要 在ROS中将点云&#xff08;PointCloud&#xff09;转换为激光扫描&#xff08;LaserScan&#xff09;是一个常见的任务&#xff0c;尤其是在某些机器人系统中&#xff0c;激光雷达数据被用于导航和避障&#x…

Windows 2003 R2与Windows 2022建立域信任报错:本地安全机构无法跟域控制器获得RPC连接。请检查名称是否可以解析,服务器是否可用。

在Windows Server 2003 R2与Windows Server 2022之间建立域信任时遇到“本地安全机构无法与域控制器获得RPC连接”的错误&#xff0c;可能是由于以下几种原因&#xff1a; DNS 解析问题&#xff1a; 确保源域和目标域的DNS配置正确&#xff0c;能够互相解析对方的域名和IP地址。…

达梦数据库的V$DM_INI和V$PARAMETER系统视图

V$DM_INI和V$PARAMETER是达梦数据库中两个常用的系统视图&#xff0c;用于查看数据库的配置参数。这两个视图的主要区别在于它们展示参数的来源和用途。 V$DM_INI V$DM_INI视图主要用于展示数据库启动时加载的初始化参数信息。这些信息通常来自于数据库的初始化参数文件&…

【运输层】TCP 的可靠传输是如何实现的?

目录 1、发送和接收窗口&#xff08;滑动窗口&#xff09; &#xff08;1&#xff09;滑动窗口的工作流程 &#xff08;2&#xff09;滑动窗口和缓存的关系 &#xff08;3&#xff09;滑动窗口的注意事项 2、如何选择超时重传时间 &#xff08;1&#xff09;加权平均往返…

MemFire Cloud让静态托管动起来!

静态托管 我们最常接触到的静态托管是github pages&#xff0c;它的常见工作模式是在github上创建一个仓库&#xff0c;使用hexo类的工具初始化仓库&#xff0c;编写markdown文件&#xff0c;生成静态页面&#xff0c;推送到github上完成页面更新&#xff0c;比如https://blog…

ViSNet:用于分子性质预测和动力学模拟的通用分子结构建模网络

编者按&#xff1a;尽管几何深度学习已经彻底颠覆了分子建模领域&#xff0c;但最先进的算法在实际应用中仍然面临着几何信息利用不足和高昂计算成本的阻碍。为此&#xff0c;微软研究院科学智能中心&#xff08;Microsoft Research AI4Science&#xff09;的研究员们提出了通用…

OCR常用识别算法综述

参考&#xff1a;https://aistudio.baidu.com/education/lessonvideo/3279888 语种&#xff1a;常用字符36与常用汉字6623&#xff0c;区别。 标注&#xff1a;文本型位置/单字符位置&#xff0c;后者标注成本大 挑战&#xff1a;场景文字识别&#xff1a;字符大小、颜色、字体…

【经典算法】LCR187:破冰游戏(约瑟夫问题,Java/C/Python3/JavaScript实现含注释说明,Easy)

目录 题目思路及实现方式一&#xff1a;迭代模拟&#xff08;用链表模拟这个游戏&#xff09;思路代码实现Java版本C语言版本Python3版本 复杂度分析 方式二&#xff1a;数学迭代思路代码实现Java版本C语言版本Python3版本 复杂度分析 方式三&#xff1a;递归思路代码实现Java版…

C语言 函数——函数的定义、调用和参数传递

目录 模块化编程&#xff08;Modular Programming&#xff09; 函数的分类 函数的定义 使用函数编程的好处 函数调用的基本方式 函数调用时的数据传递 函数调用的过程 main函数的特殊性 大话三国 分而治之 如果将main&#xff08;&#xff09;函数比作诸葛亮&#xff…

并行超算云计算使用步骤完整流程详情

本文目录 一、将项目传入并运云。二、创建项目的虚拟环境三、编辑run.sh脚本四、提交作业五、查看作业输出六、查看提交的作业号七、结束作业 一、将项目传入并运云。 二、创建项目的虚拟环境 打开终端 使用conda创建&#xff1a;conda create -n 环境名 python3.8查看conda下…

消息队列MQ的介绍和docker安装MQ

一、什么是mq? MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信&#xff0c;解耦。 二、常见的mq产品 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq RabbitMQ: One broker …

LINUX 下IPTABLES配置详解

-t<表>&#xff1a;指定要操纵的表&#xff1b; -A&#xff1a;向规则链中添加条目&#xff1b; -D&#xff1a;从规则链中删除条目&#xff1b; -i&#xff1a;向规则链中插入条目&#xff1b; -R&#xff1a;替换规则链中的条目&#xff1b; -L&#xff1a;显示规则链中…

【算法详解】二分查找

1. 二分查找算法介绍 「二分查找算法&#xff08;Binary Search Algorithm&#xff09;」&#xff1a;也叫做 「折半查找算法」、「对数查找算法」。是一种在有序数组中查找某一特定元素的搜索算法。 基本算法思想&#xff1a;先确定待查找元素所在的区间范围&#xff0c;在逐步…

界面组件DevExpress WinForms v23.2 - 功能区、富文本编辑器功能升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

软考 系统架构设计师系列知识点之云原生架构设计理论与实践(16)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之云原生架构设计理论与实践&#xff08;15&#xff09; 所属章节&#xff1a; 第14章. 云原生架构设计理论与实践 第3节 云原生架构相关技术 14.3.3 无服务器技术 1. 技术特点 2. 技术关注点 &#xff08;1&#xff…

四川一体化污水处理设备厂家如何挑选

如果您想寻找一家可靠的四川地区的污水处理设备厂家&#xff0c;以下是一些挑选的关键要素可以考虑&#xff1a; 1. 信誉和口碑&#xff1a;了解该厂家在业界的声誉和客户的评价&#xff0c;可以通过查阅相关的评论和建议&#xff0c;或者咨询其他业内人士来了解。 2. 技术实力…