AI Chat 设计模式:4. 建造者模式

news2025/1/9 1:56:07

本文是该系列的第四篇,采用问答式的方式展开,问题由我提出,答案由 Chat AI 作出,灰色背景的文字则主要是我的旁白与思考。

问题列表

  • Q.1 说说建造者模式
  • A.1
  • Q.2 “将一个复杂对象的构建过程和其表示分离”,这里不是很清楚,给我讲解一下吧
  • A.2
  • Q.3 给我举一个建造者模式的例子吧
  • A.3
  • Q.4 你的代码里,客户是通过 builderA 和 builderB 来获取汽车,似乎不太好呢
  • A.4
  • Q.5 客户端是应该通过建造者获取产品还是通过指挥者类来获取产品呢?
  • A.5
  • Q.6 我是想问 “getCar这个方法应该由客户端调用,还是由指挥者类来调用”
  • A.6
  • Q.7 复杂的产品,必须要继承自同一个产品基类吗?
  • A.7
  • Q.8 画类图
  • A.7

Q.1 说说建造者模式

A.1

建造者模式是一种创建型设计模式,它将一个复杂对象的构建过程和其表示分离,使得同样的构建过程可以创建不同的表示。这个模式的核心思想是将一个复杂的对象构建过程拆分成多个步骤,在这些步骤之间建立一定的依赖关系,最终生成一个完整的对象。

建造者模式通常包含以下几个角色:

  1. Builder(建造者):定义用于构建复杂对象的接口,包含多个抽象方法,每个方法对应一个构建步骤。

  2. ConcreteBuilder(具体建造者):实现 Builder 接口,完成复杂对象的各个构建步骤,最终生成一个合成对象。同时具有访问和获取构建结果的方法

  3. Director(指挥者):负责组合 Builder 中的各个构建步骤,最终得到复杂对象。它不直接创建对象,而是通过调用Builder的方法得到一个构建过程的完整对象。

  4. Product(产品):产品是由具体建造者生产的复杂对象,它包含了多个部件,具有一定的复杂性和功能性。

使用建造者模式可以有效地将对象的创建过程模块化,降低了复杂对象的构建难度,提高了可维护性和可扩展性。

Q.2 “将一个复杂对象的构建过程和其表示分离”,这里不是很清楚,给我讲解一下吧

A.2

当我们需要构建一个复杂对象时,可能需要通过多个步骤来完成这个过程,包括创建多个部分、添加属性、设置关联等等,这些步骤的执行顺序和具体实现可能因对象类型、使用场景等因素而不同。此时,如果将这个复杂的构建过程和最终对象的表示紧密耦合在一起,将会导致代码复杂、难以维护。

建造者模式的核心思想就是将这个复杂的构建过程和最终对象的表示分离开来,把构建复杂对象的过程拆分成一些简单的步骤,这些步骤之间不直接关联,而是在指挥者的统筹下依次调用各个步骤,最终生成构建完成的对象。

通过这种方式,建造者模式实现了构建过程和表示之间的松耦合,使得我们可以根据需要构建不同的对象,而且能够更加灵活地对构建过程进行扩展或者重构,同时也提高了代码的可读性和可维护性。

表示:其实就是我们想要产生的最终对象,也就是 Product 类对象;
构建:Builder 类就是对象的构建;
构建过程:是指 Director 类中产品构建步骤的装配顺序,该过程是稳定不会变化的。只要传递给指挥者类不同的具体建造者(ConcreteBuilder)子类,它就能构建出不同的产品(Product)。
下面举一个例子来说明。

Q.3 给我举一个建造者模式的例子吧

A.3

假设我们要创建一个汽车类,这个汽车类包含多个部件(例如车架、发动机、轮胎、油箱等等),并且每个部件的属性、数量和顺序都有所不同。

我们可以使用建造者模式来构建这个复杂对象。以下是一个简单的示例代码:

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

//定义汽车类
class Car {
public:
    //车架
    string frame;
    //发动机
    string engine;
    //轮胎
    string tire;
    //油箱
    string tank;

    //车辆信息打印函数
    void printCarInfo() {
        cout << "Car Info: " << endl;
        cout << "Frame: " << frame << endl;
        cout << "Engine: " << engine << endl;
        cout << "Tire: " << tire << endl;
        cout << "Tank: " << tank << endl;
    }
};

//定义汽车建造者类
class CarBuilder {
public:
    //构造函数
    CarBuilder() : m_car(new Car) {}

    //设置车架
    virtual void setFrame() {}
    //设置发动机
    virtual void setEngine() {}
    //设置轮胎
    virtual void setTire() {}
    //设置油箱
    virtual void setTank() {}

    //获取构造完成的车辆
    Car* getCar() {
        return m_car;
    }

protected:
    //保存构造完成的车辆
    Car* m_car;
};

//定义汽车建造者A
class CarBuilderA : public CarBuilder {
public:
    //设置车架
    void setFrame() {
        m_car->frame = "Frame A";
    }
    //设置发动机
    void setEngine() {
        m_car->engine = "Engine A";
    }
    //设置轮胎
    void setTire() {
        m_car->tire = "Tire A";
    }
    //设置油箱
    void setTank() {
        m_car->tank = "Tank A";
    }
};

//定义汽车建造者B
class CarBuilderB : public CarBuilder {
public:
    //设置车架
    void setFrame() {
        m_car->frame = "Frame B";
    }
    //设置发动机
    void setEngine() {
        m_car->engine = "Engine B";
    }
    //设置轮胎
    void setTire() {
        m_car->tire = "Tire B";
    }
    //设置油箱
    void setTank() {
        m_car->tank = "Tank B";
    }
};

//定义指挥者类
class Director {
public:
    void constructCar(CarBuilder* builder) {
        builder->setFrame();
        builder->setEngine();
        builder->setTire();
        builder->setTank();
    }
};

//主函数
int main() {
    CarBuilderA builderA;
    CarBuilderB builderB;
    Director director;

    //指挥者指定汽车建造者A来构建汽车
    director.constructCar(&builderA);
    Car* carA = builderA.getCar();
    carA->printCarInfo();

    //指挥者指定汽车建造者B来构建汽车
    director.constructCar(&builderB);
    Car* carB = builderB.getCar();
    carB->printCarInfo();
    return 0;
}

在代码中,Car类是我们所要构建的复杂对象。CarBuilder是汽车建造者的接口类,包含了构建汽车的各种方法。不同的汽车建造者继承这个接口类,并完成各自的构建过程,即不同的车架、发动机、轮胎和油箱Director类是指挥者,负责组合不同步骤的构建者来生成最终的复杂对象加粗样式

最后,我们可以使用CarBuilderACarBuilderB来构建不同的汽车对象。

这个例子还是比较好理解的,对各个类的作用也进行了讲解。
一般来说,指挥者类组合一个 builder 类的对象,同时提供一个接口让客户端来设置不同的builder具体对象。但像例子中这样不直接持有也是可以的,在使用的时候直接传入即可。
我们注意到,在 main() 函数(客户端)中,是通过 buildA、buildB 这个两个具体建造者类对象来获取产品的。这点我并不认可,因为这个例子明显是一个标准的建造者模式示例,拥有指挥者类,不应该让客户端继续与建造者类沟通。

Q.4 你的代码里,客户是通过 builderA 和 builderB 来获取汽车,似乎不太好呢

A.4

非常抱歉,我的代码确实有误,我在获取汽车对象那一段没有更新。在正确的代码中,我们应该将指挥者和具体的汽车建造者进行配合,完成具体的构建过程,最终返回构建完成的汽车对象

以下是代码的修改版本:

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

//定义汽车类
class Car {
public:
    //车架
    string frame;
    //发动机
    string engine;
    //轮胎
    string tire;
    //油箱
    string tank;

    //车辆信息打印函数
    void printCarInfo() {
        cout << "Car Info: " << endl;
        cout << "Frame: " << frame << endl;
        cout << "Engine: " << engine << endl;
        cout << "Tire: " << tire << endl;
        cout << "Tank: " << tank << endl;
    }
};

//定义汽车建造者类
class CarBuilder {
public:
    //构造函数
    CarBuilder() : m_car(new Car) {}

    //设置车架
    virtual void setFrame() {}
    //设置发动机
    virtual void setEngine() {}
    //设置轮胎
    virtual void setTire() {}
    //设置油箱
    virtual void setTank() {}

    //获取构造完成的车辆
    Car* getCar() {
        return m_car;
    }

protected:
    //保存构造完成的车辆
    Car* m_car;
};

//定义汽车建造者A
class CarBuilderA : public CarBuilder {
public:
    //设置车架
    void setFrame() {
        m_car->frame = "Frame A";
    }
    //设置发动机
    void setEngine() {
        m_car->engine = "Engine A";
    }
    //设置轮胎
    void setTire() {
        m_car->tire = "Tire A";
    }
    //设置油箱
    void setTank() {
        m_car->tank = "Tank A";
    }
};

//定义汽车建造者B
class CarBuilderB : public CarBuilder {
public:
    //设置车架
    void setFrame() {
        m_car->frame = "Frame B";
    }
    //设置发动机
    void setEngine() {
        m_car->engine = "Engine B";
    }
    //设置轮胎
    void setTire() {
        m_car->tire = "Tire B";
    }
    //设置油箱
    void setTank() {
        m_car->tank = "Tank B";
    }
};

//定义指挥者类
class Director {
public:
    Car* constructCar(CarBuilder* builder) {
        builder->setFrame();
        builder->setEngine();
        builder->setTire();
        builder->setTank();
        return builder->getCar();
    }
};

//主函数
int main() {
    CarBuilderA builderA;
    CarBuilderB builderB;
    Director director;

    //指挥者指定汽车建造者A来构建汽车
    Car* carA = director.constructCar(&builderA);
    carA->printCarInfo();

    //指挥者指定汽车建造者B来构建汽车
    Car* carB = director.constructCar(&builderB);
    carB->printCarInfo();
    return 0;
}

在新的代码中,Director类的constructCar函数接受一个CarBuilder指针参数,用于在执行构建过程并返回构建成功的汽车对象。客户端代码现在可以通过Director来调用不同的CarBuilder来获得不同类型的汽车对象了。

既然分离出了指挥者类,就应该通过指挥者类来获取最终表示,而不应该让客户端继续调用建造者类。当然,如果,没有分离出指挥者类,那自然是没任何问题的,此处不再展开。
为了确定,AI 不是屈服于我作出的违心答案,我还将继续追问这一点。

Q.5 客户端是应该通过建造者获取产品还是通过指挥者类来获取产品呢?

A.5

客户端应该通过指挥者类来获取产品,而不是直接通过建造者获取产品。

指挥者类的作用是协调建造者中的各个构建步骤,确保整个构建过程的正确执行。客户端如果直接通过建造者来获取产品,那么就需要了解建造者的具体实现细节,带来了一定的耦合性,同时也不利于后期的维护和扩展。

通过指挥者类来获取产品,客户端只需要与指挥者类进行交互,即使建造者的实现发生了改变,也不会对客户端造成影响。同时,指挥者类也可以封装建造者构建对象的过程,提供更加方便的接口给客户端使用。

我感觉它这里回答的有问题,尤其是斜体字部分,或许它没有理解我的问题。客户端即使通过建造者类来获取产品,也不需要了解具体实现细节,只是调用 getCar() 接口而已。因此,建造者的实现发生了改变,对客户端的调用也不会有任何影响。
这就是 AI 一本正经的胡说八道吧。

Q.6 我是想问 “getCar这个方法应该由客户端调用,还是由指挥者类来调用”

A.6

getCar 方法应该由指挥者类来调用,这也是指挥者类的作用的一部分。

由于指挥者类负责协调建造者的构建步骤,最终将构建完成的产品组装起来,因此在 constructCar 方法中应该调用建造者的各个构建方法,并在完成构建之后,从建造者对象中获取构建完成的产品。指挥者类可以把获得的产品返回给客户端,或者进一步处理之后返回。因此,在 constructCar 方法中的返回值就是通过建造者构建好的产品。

对于客户端调用建造者的 getCar 方法来获得产品,会暴露建造的过程细节,这样会导致建造者与客户端之间的紧耦合。

现在它算是明确回答了我的问题,应该让指挥者类来返回成品,别让客户端再调用 getCar() 方法了。但“会暴露建造的过程细节”我是不完全认同的,也就是多暴露一个 getCar() 方法罢了,还称不上实现细节,耦合确实加重了。

Q.7 复杂的产品,必须要继承自同一个产品基类吗?

A.7

在建造者模式中,复杂的产品并不一定必须继承自同一个产品基类建造者模式关注的是构建过程和组装复杂对象,而不是具体的产品类型

建造者模式允许构建不同类型的产品,这些产品可以是无关的、不具备共同特征的。如果产品之间没有共同的属性或行为,那么它们可以是独立的类,而不需要继承自同一个基类。

然而,如果多个产品类型具有一些共同的属性或行为,使用继承来实现一个产品基类是合理的。通过继承,可以提取和共享共同的代码,简化代码结构,同时也方便对产品进行统一管理和扩展。

继承产品基类有利于实现多态,使得客户端可以以统一的方式操作不同类型的产品。此外,基类也可以定义一些虚拟方法,供子类进行自定义实现。

所以,是否需要继承自同一个产品基类取决于具体的设计需求和产品结构,根据实际情况来决定。

关注的不是产品类型,而是产品的构建步骤,只要有相同的构建步骤,就可以使用建造者模式。

Q.8 画类图

A.7

要记住:并不要求创建的产品继承自同一个基类,只是我们这个例子恰好如此。

在这里插入图片描述

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

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

相关文章

2023年软考一般什么时候出成绩呢?在线蹬?

软考成绩一般在考试结束后的两个月内公布&#xff0c;而且每次查询时间并不是固定的&#xff0c;比如上半年5月软考考试成绩查询时间一般是7月初&#xff0c;下半年11月初软考成绩查询时间一般就在12月底左右&#xff08;如图所示&#xff09; 过了23年上半年的软考&#xff0c…

电动力学复习提纲

电动力学复习提纲 电动力学核心知识点 电磁场能量密度和能流 场和电荷的能量守恒电磁场的能量密度和能流密度 电偶极矩&#xff0c;磁偶极矩和电四极矩 电极化强度矢量与电偶极矩 磁化强度与磁偶极矩 电势的多级展开 磁矢势的多级展开 非相对论体系的电磁能 静电能 小区…

13万字智慧能源大数据分析平台及能源集团数字化平台建设方案

导读&#xff1a;原文《13万字智慧能源大数据分析平台及能源集团数字化平台建设方案》共335页word&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 目录 智慧能源大数…

干货:在校园里如何创业做外卖平台

干货&#xff1a;在校园里如何创业做外卖平台 外卖能够满足大学生消费者在足不出寝的情况下&#xff0c;方便省时的用餐需求&#xff0c;这也是外卖市场在大学快速发展的主要原因。有关数据显示&#xff0c;40.3%大学生群体月均点外卖次数在5-10次之间&#xff0c;37%40.3%大学…

Mybatis处理复杂查询环境

复杂查询环境 多对一和一对多 多个工作者对应一个协调人 对于工作者(worker)而言&#xff0c;多个工作者关联一个协调人(coordinator)&#xff08;多对一&#xff0c;关联&#xff09; 对于协调人而言&#xff0c;一个协调人有很多工作者&#xff08;一对多&#xff0c;集合&a…

SQL Server表分区

什么是表分区 一般情况下&#xff0c;我们建立数据库表时&#xff0c;表数据都存放在一个文件里。 但是如果是分区表的话&#xff0c;表数据就会按照你指定的规则分放到不同的文件里&#xff0c;把一个大的数据文件拆分为多个小文件&#xff0c;还可以把这些小文件放在不同的…

jvm自带故障处理工具之jmap

jmap是java虚拟机自带的一种内存映像工具。 一般通过jmap可以生成堆的当前使用情况的快照&#xff0c;然后用它来分析或者调优JVM内存使用。 jmap -help jmap -histo:live pid 打印堆的直方图。对于每个Java类&#xff0c;将打印对象数&#xff0c;以字节为单位的内存大小以…

ROS:API操作

目录 一、初始化1.1C版1.2Python版 二、话题与服务相关对象2.1发布对象2.1.1C版2.1.2Python版 2.2订阅对象2.2.1C版2.2.2Python版 2.3服务对象2.3.1C版2.3.2Python版 2.4客户端对象2.4.1C版2.4.2Python版 三、回旋函数3.1C版3.1.1spinOnce()3.1.2spin()3.1.3二者比较 3.2Python…

安装Spark

安装Spark 任务描述 本关任务&#xff1a;完成Spark安装。 相关知识 Apache Spark 是一个新兴的大数据处理通用引擎&#xff0c;提供了分布式的内存抽象。Spark 最大的特点就是快&#xff0c;可比 Hadoop MapReduce 的处理速度快 100 倍。本节将详细说明 Spark 的安装。 下…

C++STL算法之堆算法

堆算法 堆堆算法函数堆算法函数的使用make_heappush_heappop_heapsort_heap 堆 堆就是如图&#xff0c;像这样一种连续的数据&#xff0c;但是注意0的位置不存储数据&#xff0c;目的是为了让编号一置 这里介绍两个概念 大顶堆&#xff1a; 一段内存在二叉数的基础上有序&#…

原生 js (前后端不分离)复选框动态控制表格列的显示隐藏 存储状态

写个div 绑定点击事件 点击设置按钮 让div和复选框显示 <div class"control" οnclick"controlClk()">❁</div> init()// var storeCheckbox window.sessionStorage.getItem(storeCheckbox);function getStore(name) {var storeCheckbox win…

趣谈MySQL 多个%等模糊查询

一、建表并插入数据 1、创建一个people表 DROP TABLE IF EXISTS people; CREATE TABLE people (id int NOT NULL COMMENT 主键,name varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 姓名,sex tinyint NOT NULL COMMENT 性别,age int NOT N…

怎样优雅地增删查改(一):从0开始搭建Volo.Abp项目

软件系统中数据库或者持久层的基本操作功能可以用Curd描述&#xff0c;Curd即 增加&#xff08;Create&#xff09;、更新&#xff08;Update&#xff09;、读取查询&#xff08;Retrieve&#xff09;和删除&#xff08;Delete&#xff09;&#xff0c; 这4个单词的首字母。 在…

Elasticsearch(2)——映射关系

1 什么是映射 映射&#xff08;mapping&#xff09;就像数据库中的 Schema &#xff0c;描述了文档可能具有的字段或属性、每个字段的 数据类型&#xff0c;比如 Text&#xff0c;Keyword&#xff0c;Integer 或 Date &#xff0c;以及 Lucene 是如何索引和存储这些字 段的。 …

VS Code安装及配置教程(Windows)

VS Code安装教程&#xff08;Windows&#xff09; 一、下载1、官网下载&#xff1a;2、网盘下载 二、安装 一、下载 1、官网下载&#xff1a; 点击跳转 2、网盘下载 点击跳转 二、安装 双击运行程序&#xff0c;进行安装&#xff0c;同意许可&#xff0c;点击下一步 选择…

轻松拿捏Jenkins(Linux)+Java11+Docker自动化打包部署

注&#xff1a;本文由笔者小白亲手实践写作&#xff0c;涵盖较全&#xff0c;创作不易&#xff0c;且行且珍惜&#xff01;&#xff01; 历时24小时&#xff0c;包含Jenkins环境构建部署过程中的99%问题&#xff0c;确定不来看一下吗&#xff1f; 文章目录 开篇一、软件包准备J…

华为OD机试真题 Python 实现【任务总执行时长】【2023Q1 100分】

目录 一、题目描述二、输入描述三、输出描述四、补充说明五、解题思路六、Python算法源码七、效果展示1、输入2、输出3、说明 一、题目描述 任务编排服务负责对任务进行组合调度。 参与编排的任务有两种类型&#xff0c;其中一种执行时长为taskA&#xff0c;另一种执行时长为…

Linux项目自动化构建工具——make和makefile

make和makefile 一.基本使用二.make并不是每一次都会进行编译三.原理四.特殊符号 一.基本使用 首先创建一个mycode.c文件&#xff0c;接着使用vim写几行代码。 接着创建一个makefile文件&#xff08;这里的m大写小写均可但需要在当前目录下&#xff09;&#xff0c;并使用vim进…

C语言 - unicode编码与字符串互转

概述 最近项目上需要unicode编码与字符串互转&#xff0c;在此做个笔录。 1、编码环境为 visual studio 21&#xff0c;code如下&#xff1a; #include <stdlib.h> #include <stdio.h> #include <string.h> #include <wchar.h> #include <locale.…

Java SpringBoot集成WebSocket

SpringBoot集成WebSocket 引入依赖WebSocket配置拦截器配置总体配置类WebSocket服务类 控制层测试测试使用在线连接工具&#xff08;自行百度&#xff09;连接地址&#xff1a;ws://IP端口号/websocket测试发送消息测试 公众号&#xff1a;一颗星宇宙 引入依赖 <dependency…