创建型模式--4.抽象工厂模式【弗兰奇一家】

news2024/12/23 4:29:48

1. 奔向大海

在海贼世界中,位于水之都的弗兰奇一家是由铁人弗兰奇所领导的以拆船为职业的家族,当然了他们的逆向工程做的也很好,会拆船必然会造船。船是海贼们出海所必备的海上交通工具,它由很多的零件组成,从宏观上看它有这么几个组成部分:船体、动力系统、武器。
在这里插入图片描述

有一天我攒够了钱要出海,找到了弗兰奇一家,发现他们的老大跟着草帽路飞出海了,但是我还是选择相信他们的技术。下面是他们给我制定的造船方案,根据我的购买力提供了不同型号的海贼船,一共是三个级别,如下表:

基础型标准型旗舰型
船体木头钢铁合成金属
动力手动内燃机核能
武器速射炮激光

根据这个表,在造船的时候需要根据不同的型号选择相应的零部件,在设计程序的时候还需要保证遵循开放-封闭原则,即添加了新型号之后不需要修改原有代码,而是添加新的代码。

1.1 船体

因为要建造的这艘船是由多个部件组成的并且每个部件还有不同的品级可供选择,先说船体,关于船体材料的这个属性是可变的,所以还需要给它提供一个抽象类,这样在这个抽象类的子类中就可以更换不同的船体材料了:

// 船体
class ShipBody
{
public:
    virtual string getShipBody() = 0;
    virtual ~ShipBody() {}
};

class WoodBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<木材>制作轮船船体...");
    }
};

class IronBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<钢铁>制作轮船船体...");
    }
};

class MetalBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<合金>制作轮船船体...");
    }
};

这样,只要添加了新的造船材料,就给它添加一个对应的子类(父类是 ShipBody),在这个子类重写父类的虚函数getShipBody(),用这种材料把船体造出来就行了。

1.2 动力和武器

知道了如何处理船体部分,那么动力武器部分的处理思路也是一样的:

  • 可以给船提供不同的动力系统,因此这个属性是可变的,所以需要提供一个抽象类
  • 可以给船提供不同的武器系统,因此这个属性也是可变的,所以也需要提供一个抽象类

照葫芦画瓢把代码写一下:

// 动力
class Engine
{
public:
    virtual string getEngine() = 0;
    virtual ~Engine() {}
};

class Human : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<人力驱动>...");
    }
};

class Diesel : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<内燃机驱动>...");
    }
};

class Nuclear : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<核能驱动>...");
    }
};

// 武器
class Weapon
{
public:
    virtual string getWeapon() = 0;
    virtual ~Weapon() {}
};

class Gun : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<枪>");
    }
};

class Cannon : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<自动机关炮>");
    }
};

class Laser : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<激光>");
    }
};

不论是动力还是武器系统都是需要提供一个抽象类,这样它们的子类就可以基于这个抽象基类进行专门定制,如果要对它们进行拓展也只需添加新的类,不需要修改原有代码。

1.3 一艘船

如果有了以上的零件,只需要在工厂中将它们装配到一起,这样就得到了一艘船,这是一艘什么型号的船取决于使用的是什么零件,所以只需要让这艘船对应一个类就可以了,这个类的定义如下:

// 轮船类
class Ship
{
public:
    Ship(ShipBody* body, Weapon* weapon, Engine* engine) :
        m_body(body), m_weapon(weapon), m_engine(engine) 
    {
    }
    string getProperty()
    {
        string info = m_body->getShipBody() + m_weapon->getWeapon() + m_engine->getEngine();
        return info;
    }
    ~Ship() 
    {
        delete m_body;
        delete m_engine;
        delete m_weapon;
    }
private:
    ShipBody* m_body = nullptr;
    Weapon* m_weapon = nullptr;
    Engine* m_engine = nullptr;
};

这艘船使用的零件是通过构造函数参数传递进来的,并在类的内部对这些零件对象进行了保存,这样在释放船这个对象的时候就可以将相应的零件对象一并析构了。

另外,在Ship这个类中保存零件对象的时候使用的是它们的父类指针,这样就可以实现多态了。

2. 准备生产

万事俱备,只剩建厂了。造船厂要生产三种型号的船,那么也就是至少需要三条生产线,所以对应的工厂类也就不止一个,处理思路还是一样的,提供一个抽象的基类,然后在它的子类中完成各种型号的船的组装,每个子类对应的就是一条生产线

2.1 设计图纸

现在,关于抽象工厂模式的逻辑应该是比较清晰了,下面来看一下这个模式对应的UML类图:

在这里插入图片描述

在这个图中有四个抽象类,分别是:

  1. ShipBody 类:船体的抽象类
    有三个子类,在子类中通过不同的材料来建造船体
  2. Weapon 类:武器的抽象类
    有三个子类,在子类中给战船提供不同种类的武器
  3. Engine 类:动力系统抽象类
    有三个子类,在子类中给战船提供不同动力系统
  4. AbstractFactory 类:抽象工厂类
    在子工厂类中生产不同型号的战船
    ShipBodyWeaponEngine有依赖关系,在工厂函数中创建了它们的实例对象
    Ship 类有依赖关系,在工厂函数中创建了它的实例对象

关于Ship类它可以和ShipBodyWeaponEngine可以是聚合关系,也可以是组合关系:

  • 组合关系:析构Ship类对象的时候,也释放了ShipBody 、Weapon、Engine对象
  • 聚合关系:析构Ship类对象的时候,没有释放ShipBody 、Weapon、Engine对象
    在上面的Ship类的析构函数中做了释放操作,因此在UML中将它们之间描述为了组合关系。

在使用抽象工厂模式来处理实际问题的时候,由于实际需求不一样,我们画出的UML类图中有些类和类之间的关系可能也会有所不同,所以上图只适用于当前的业务场景,在处理其他需求的时候还需要具体问题具体分析。

2.2 开工

给上面的程序再添加相应的工厂类,就可以生产出我们需要的型号的船只了,示例代码如下:

#include <iostream>
#include <string>
using namespace std;

// 船体
class ShipBody
{
public:
    virtual string getShipBody() = 0;
    virtual ~ShipBody() {}
};

class WoodBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<木材>制作轮船船体...");
    }
};

class IronBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<钢铁>制作轮船船体...");
    }
};

class MetalBody : public ShipBody
{
public:
    string getShipBody() override
    {
        return string("用<合金>制作轮船船体...");
    }
};

// 武器
class Weapon
{
public:
    virtual string getWeapon() = 0;
    virtual ~Weapon() {}
};

class Gun : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<枪>...");
    }
};

class Cannon : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<自动机关炮>...");
    }
};

class Laser : public Weapon
{
public:
    string getWeapon() override
    {
        return string("配备的武器是<激光>...");
    }
};

// 动力
class Engine
{
public:
    virtual string getEngine() = 0;
    virtual ~Engine() {}
};

class Human : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<人力驱动>...");
    }
};

class Diesel : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<内燃机驱动>...");
    }
};

class Nuclear : public Engine
{
public:
    string getEngine() override
    {
        return string("使用<核能驱动>...");
    }
};

// 轮船类
class Ship
{
public:
    Ship(ShipBody* body, Weapon* weapon, Engine* engine) :
        m_body(body), m_weapon(weapon), m_engine(engine) 
    {
    }
    string getProperty()
    {
        string info = m_body->getShipBody() + m_weapon->getWeapon() + m_engine->getEngine();
        return info;
    }
    ~Ship() 
    {
        delete m_body;
        delete m_engine;
        delete m_weapon;
    }
private:
    ShipBody* m_body = nullptr;
    Weapon* m_weapon = nullptr;
    Engine* m_engine = nullptr;
};

// 工厂类
class AbstractFactory
{
public:
    virtual Ship* createShip() = 0;
    virtual ~AbstractFactory() {}
};

class BasicFactory : public AbstractFactory
{
public:
    Ship* createShip() override
    {
        Ship* ship = new Ship(new WoodBody, new Gun, new Human);
        cout << "<基础型>战船生产完毕, 可以下水啦..." << endl;
        return ship;
    }
};

class StandardFactory : public AbstractFactory
{
public:
    Ship* createShip() override
    {
        Ship* ship = new Ship(new IronBody, new Cannon, new Diesel);
        cout << "<标准型>战船生产完毕, 可以下水啦..." << endl;
        return ship;
    }
};

class UltimateFactory : public AbstractFactory
{
public:
    Ship* createShip() override
    {
        Ship* ship = new Ship(new MetalBody, new Laser, new Nuclear);
        cout << "<旗舰型>战船生产完毕, 可以下水啦..." << endl;
        return ship;
    }
};

int main()
{
    AbstractFactory* factroy = new StandardFactory;
    Ship* ship = factroy->createShip();
    cout << ship->getProperty();
    delete ship;
    delete factroy;
    return 0;
}

main()函数中,要通过工厂类的工厂函数生产什么型号的战船,和用户的需求息息相关,所以这个选择也是用户通过客户端的操作界面做出的,在这个例子中,关于客户端的界面操作就直接忽略了。

抽象工厂模式适用于比较复杂的多变的业务场景,总体上就是给一系列功能相同但是属性会发生变化的组件(如:船体材料、武器系统、动力系统)添加一个抽象类,这样就可以非常方便地进行后续的拓展,再搭配工厂类就可以创建出我们需要的对象了。

关于简单工厂模式、工厂模式和抽象工厂模式的区别可以做如下总结:

  1. 简单工厂模式不能遵守开放-封闭原则,工厂和抽象工厂模式可以
  2. 简单工厂模式只有一个工厂类,工厂和抽象工厂有多个工厂类
  3. 工厂模式创建的产品对象相对简单,抽象工厂模式创建的产品对象相对复杂
    工厂模式创建的对象对应的类不需要提供抽象类【这产品类组件中没有可变因素】
    抽象工厂模式创建的对象对应的类有抽象的基类【这个产品类组件中有可变因素】

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

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

相关文章

41---音频电路设计

视频链接 音频电路设计01_哔哩哔哩_bilibili 音频电路设计 1、音频基本介绍 1.1、设备 1.1.1、音频接口 型号&#xff1a;ABA-JAK-038-K44 电脑主机上的音频输出插口&#xff0c;一个是粉色的&#xff0c;用来连接麦克风或话筒&#xff0c;一个是绿色的&#xff0c;用来连…

item_review获取商品评论API接口采集用户评论数据接入示例

要获取商品评论API接口采集用户评论数据&#xff0c;首先需要了解API的具体请求方式、参数以及返回数据格式。以下是一个示例&#xff08;获取API接入KEY&#xff09;&#xff1a; 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secret…

leetcode之704 二分查找

文章目录 每日碎碎念一、题目要求及测试点704 二分查找测试点提示 二、题解自己上手正经题解二分法之左闭右闭二分法之左闭右开 三、总结 每日碎碎念 苦痛生活今日起 从今天开始LeetCode打卡&#xff0c;一边重新刷题&#xff0c;一边春招等笔面试&#xff0c;人生苦痛… 一、…

可视化大屏的应用(9):设备运行监控的应用案例

通过可视化大屏&#xff0c;监控人员可以更加直观地了解设备的运行情况&#xff0c;及时发现问题并进行处理&#xff0c;提高设备的稳定性和可靠性&#xff0c;大千UI工场本期带来相关利用的案例&#xff0c;欢迎友友们品鉴。 可视化大屏在设备运行监控领域有以下作用&#xf…

数据结构:构建完全二叉查找树

文章目录 1、步骤 1: 对给定数组排序2、步骤 2: 递归构建完全二叉查找树3、注意4、在有序数组中寻找根结点位置5、代码实现6、其他方法&#xff1f;基本思路插入操作删除操作特别考虑 对于一个给定序列的二叉查找树&#xff0c;有很多种&#xff0c;但是完全二叉查找树只有一种…

springboot 整合 mybatis(配置版)

代码及配置整合 创建实体类,与数据库对应 创建 mapper、service 和 controller @AutowiredUserService userService;@ResponseBody@GetMapping("/user")public com.vazquez.bootstudy.model.User getById(@RequestParam("id") Long id) {return userServ…

AI大模型基石:文字与数字的起源与演变

AI大模型基石&#xff1a;文字与数字的起源与演变 1、文字 1.1、起源 我们的祖先在还没有发明文字和语言之前就已经开始使用“咿咿呀呀”的声音来传播信息了&#xff0c;比如在野外活动遇到危险&#xff0c;然后发出“咿咿呀呀”的声音来提醒同伴小心&#xff0c;同伴在接收到…

物联网数据服务平台

随着物联网技术的迅猛发展&#xff0c;海量数据的产生和应用成为推动工业数字化转型的核心动力。在这个数据为王的时代&#xff0c;如何高效地收集、处理、分析并应用这些数据&#xff0c;成为了企业关注的焦点。物联网数据服务平台应运而生&#xff0c;为企业提供了全面、高效…

20240403-算法复习打卡day43||● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

1049. 最后一块石头的重量 II class Solution { public:int lastStoneWeightII(vector<int>& stones) {vector<int> dp(15001, 0);int sum 0;for (int i 0; i < stones.size(); i) sum stones[i];int target sum / 2;for (int i 0; i < stones.siz…

群晖NAS使用Docker部署Potopea在线图片编辑工具并实现公网访问

文章目录 1. 部署Photopea2. 运行Photopea3. 群晖安装Cpolar4. 配置公网地址5. 公网访问测试6. 固定公网地址 本文主要介绍如何在群晖NAS使用Docker部署Potopea在线图片编辑工具&#xff0c;并结合cpolar内网穿透实现公网环境可以远程访问本地部署的Potopea. Photopea是一款强大…

鸿蒙OS实战开发:【多设备自适应服务卡片】

介绍 服务卡片的布局和使用&#xff0c;其中卡片内容显示使用了一次开发&#xff0c;多端部署的能力实现多设备自适应。 用到了卡片扩展模块接口&#xff0c;[ohos.app.form.FormExtensionAbility] 。 卡片信息和状态等相关类型和枚举接口&#xff0c;[ohos.app.form.formInf…

设计模式 -- 发布订阅模式

发布订阅模式&#xff1a; 订阅者把自己想订阅的事件注册到调度中心&#xff0c;当发布者发布该事件到调度中心&#xff0c;也就是该事件触发时&#xff0c;由调度者统一调度订阅者注册到调度中心的处理代码。 在javaScript 中我们一般使用事件模型来代替传统的发布订阅模式。 …

最长上升子序列(线性dp)-java

主要是解决最长上升子序列问题&#xff0c;推出状态转移方程。 文章目录 前言 一、最长上升子序列问题 二、算法思路 1.最长上升子序列思路 三、代码如下 1.代码如下&#xff08;示例&#xff09;&#xff1a; 2.读入数据 3.代码运行结果 总结 前言 主要是解决最长上升子序列问…

三相交流电子负载的基础认识

三相交流电子负载主要用于电源、电机、逆变器等产品的测试和老化&#xff0c;它能够精确地模拟各种负载的工作状态&#xff0c;如阻性、感性、容性等&#xff0c;以满足不同产品的测试需求。三相交流电子负载具有响应速度快、精度高、稳定性好等特点&#xff0c;是现代电力电子…

6款超好用AI写作神器,写作效率秒拔高! #经验分享#人工智能#知识分享

在当今信息爆炸的时代&#xff0c;写作成为了人们表达思想、分享知识和传递情感的重要方式之一。对于很多人来说&#xff0c;写作并非易事。我们会陷入困境&#xff0c;无法找到灵感&#xff0c;我们会苦恼于语言表达的准确性&#xff0c;还有时候我们可能遭遇到了创作瓶颈&…

功耗低、触控灵敏度高、抗干扰能力强等众多优势,输出方式多样的单键电容式触控芯片TS223B介绍

•应用领域• 适用于小家电、电子玩具、智能物联网等各种触控产品方案。 •功能介绍• 单键电容式触控芯片TS223B具有功耗低、触控灵敏度高、抗干扰能力强等众多优势&#xff0c;输出方式包括直接输出、电平翻转输出&#xff0c;并且输出的初始状态可以配置&#xff0c;能灵活满…

UVA12538 Version Controlled IDE 题解 crope

Version Controlled IDE 传送门 题面翻译 维护一种数据结构&#xff0c;资磁三种操作。 1.在p位置插入一个字符串s 2.从p位置开始删除长度为c的字符串 3.输出第v个历史版本中从p位置开始的长度为c的字符串 1 ≤ n ≤ 50000 1 \leq n \leq 50000 1≤n≤50000&#xff0c;所…

Spring声明式事务(Spring学习笔记十三)

不推荐使用编程式事务 在Spring-dao.xml中配置声明式事务 <!--配置声明式事务 --><!--获得transactionManager然后把他丢给他的构造器 constructor-arg --><bean id"transactionManager" class"org.springframework.jdbc.datasource.Data…

网站压力测试和Locust

一、压力测试介绍 网站压力测试是一种评估网站性能、可靠性和稳定性的方法。它通过模拟大量用户同时访问网站,来测试网站的响应时间、吞吐量、资源利用率等指标,从而发现网站的潜在问题和瓶颈。下面我将从几个方面详细介绍网站压力测试: 1、压力测试的目的 评估网站在高并发…

Midjourney该怎么用?从零基础到落地实践

前言 从注册登录到基本的操作界面&#xff0c;提示词组成后缀介绍&#xff0c;到主流的生成图片的方式&#xff0c;以及最重要的提示词咒语分享&#xff0c;还有一些我的使用心得&#xff0c;希望对大家有帮助&#xff01; 喜欢的话欢迎关注我&#xff0c;欢迎点赞收藏评论&am…