【设计模式-6】建造者模式的实现与框架中的应用
建造者模式又被成为生成器模式,是一种使用频率比较低,相对复杂的创建型模式,在很多源码框架中可以看到建造者的使用场景,稍后我们会在本文末尾展示几个框架的使用案例。
建造者模式所构造的对象通常是比较复杂而且庞大的,也是按照既定的构建顺序将对象中的各个属性组装起来。与工厂模式不同的是,建造者模式主要目的是把繁琐的构建过程从产品类和工厂类中抽离出来,进一步解耦,最终实现用一套标准的制造工序制造出不同的产品。
1. 定义
建造者模式 的官方定义是:将一个复杂对象的构建和表示分离,使得同样的构建过程可以创建不同的表示。建造者模式一步一步地创建一个复杂的对象,它允许用户通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的构造细节。
官方的表达还是挺难理解的,总体意思就是,构建过程固定,构建使用的每个组件都可以通过继承或者实现 呈现出多态的特性,这依赖于抽象建造者接口和具体的建造者实现类两个组件来表示。
在建造者模式中,通常包含4个角色:
- 产品:复杂的产品类,建造者的最终产物,构建过程相对复杂,需要很多组件组装而成。
- 抽象建造者:建造者接口,包含两个方便,一个是构成产品的各个属性,一个是各个属性的构建步骤方法。
- 建造者实现:建造者接口的实现类,可以有多个,对应不同的组件实现细节,但是不会包含构建的顺序逻辑。
- 指导者:客户端依赖的创建产品的指导者类,指导者类会通过建造者接口引入具体的建造者实现,并且包含了具体的构建顺序。
2. 代码实现
建造者的实现逻辑想对复杂,下面展示一个酒水制造的代码案例,某个酒厂生产啤酒和红酒两类产品,对应上述的四个角色,代码实现如下:
- 1. 产品
// 产品对象
public class Wine {// 用list封装制作工艺,表示制作的顺序是固定的private List<String> list = new ArrayList();// 1.准备原材料public void setPrepareMaterial(String prepareMaterial) {list.add(prepareMaterial);}// 2.制作工艺public void setCraftsmanShip(String craftsmanShip) {list.add(craftsmanShip);}// 3.出厂包装public void setFactoryPackage(String factoryPackage) {list.add(factoryPackage);}@Overridepublic String toString() {return "Wine{" + "list=" + list + '}';}
}
- 2. 抽象建造者
// 抽象的建造者
public abstract class AbstractBuilder {// 自定义产品Wine wine = new Wine();// 设置原材料public abstract void setPrepareMaterial();// 设置制作工艺public abstract void setCraftsmanShip();// 设置出厂包装public abstract void setFactoryPackage();// 获取产品 - 注意这里是个钩子方法,子类可以不实现public Wine getProduct() {return wine;}
}
- 3. 建造者实现
// 啤酒建造者-具体的子类实现
public class BeerBuilder extends AbstractBuilder {@Overridepublic void setPrepareMaterial() {wine.setPrepareMaterial("1.准备原材料:小麦 + 豆子");}@Overridepublic void setCraftsmanShip() {wine.setCraftsmanShip("2.制作工艺:发酵 + 蒸馏");}@Overridepublic void setFactoryPackage() {wine.setFactoryPackage("3.出厂包装:易拉罐 + 纸箱");}
}// 葡萄酒制造者 - 具体的子类实现
public class GrapeBuilder extends AbstractBuilder{@Overridepublic void setPrepareMaterial() {wine.setPrepareMaterial("1.准备原材料:葡萄 + 酵母");}@Overridepublic void setCraftsmanShip() {wine.setCraftsmanShip("2.制作工艺:发酵 + 地下贮存");}@Overridepublic void setFactoryPackage() {wine.setFactoryPackage("3.出厂包装:玻璃瓶 + 木箱");}
}
- 4. 指导者
// 指挥者 - 构造产品对象
public class Director {private AbstractBuilder abstractBuilder;public Director(AbstractBuilder abstractBuilder) {this.abstractBuilder = abstractBuilder;}// 这里固定制造的工序,按照固定步骤执行public Wine createProduct() {// 第一步:设置原材料abstractBuilder.setPrepareMaterial();// 第二步:设置制作工艺abstractBuilder.setCraftsmanShip();// 第三步:设置出厂包装abstractBuilder.setFactoryPackage();// 第四步,返回产品return abstractBuilder.getProduct();}
}
- 客户端
// 客户端
public class Client {public static void main(String[] args) {// 啤酒制造Director beerDirector = new Director(new BeerBuilder());Wine beer = beerDirector.createProduct();System.out.println(beer);// 红酒制造Director grapeDirector = new Director(new GrapeBuilder());Wine grape = grapeDirector.createProduct();System.out.println(grape);}
}
3. UML类图
对照上述的代码Demo,我们来把这个案例的类图画出来,对应如下:

4. 简化
因为建造者是属于相对复杂的一种模式,在实际的应用当中有很多种简化的写法,比如可以忽略指导者类、建造者接口等等。下面是一种实现方式,这种实现方式在很多源码中可以看到具体实现。
通常在产品类的构造函数参数超过4个,而且这些参数中有一些是必填项,考虑使用这种建造者模式。现在我们来构建一个电脑的产品对象。
// 电脑产品对象
public class Computer {private String cpu; // 必选private String ram; // 必选private String keyboard; // 可选private String mouse; // 可选private String display; // 可选
}
这种bean类的属性设置有两种方式,一个是构造函数入参,一个是通过set()方法入参,这两种方式都有些问题。构造函数入参,当参数较多的时候,类型相同的情况下,属性的顺序容易混乱;第二个中set()方式,一个对象会支持在很多模块中设置,因为类中的属性是可以分布设置的,所以容易出现属性状态变化造成错误。
下面是一种简易的建造者实现方式(在实际场景中可能属性的构建过程很复杂):
- 建造者模式
// 电脑产品对象
public class Computer {private final String cpu; // 必选private final String ram; // 必选private final String keyboard; // 可选private final String mouse; // 可选private final String display; // 可选private Computer(Computer.Builder builder) {this.cpu = builder.cpu;this.ram = builder.ram;this.keyboard = builder.keyboard;this.mouse = builder.mouse;this.display = builder.display;}public static Computer.Builder builder(String cpu, String ram) {return new Computer.Builder(cpu, ram);}public static class Builder {private String cpu; // 必选private String ram; // 必选private String keyboard; // 可选private String mouse; // 可选private String display; // 可选Builder(String cpu, String ram) {this.cpu = cpu;this.ram = ram;}public Builder cpu(String cpu) {this.cpu = cpu;return this;}public Builder ram(String ram) {this.ram = ram;return this;}public Builder keyboard(String keyboard) {this.keyboard = keyboard;return this;}public Builder mouse(String mouse) {this.mouse = mouse;return this;}public Builder display(String display) {this.display = display;return this;}public Computer build() {return new Computer(this);}}@Overridepublic String toString() {return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", keyboard='" + keyboard + '\''+ ", mouse='" + mouse + '\'' + ", display='" + display + '\'' + '}';}
}
- 客户端实现
// 客户端
public class Client {public static void main(String[] args) {Computer computer = Computer.builder("cpu", "ram").cpu("cpu-1").mouse("mouse").display("display").build();System.out.println(computer);}
}
5. 总结
建造者模式的核心在于如何一步一步地构建一个包含多个组成部件的完整对象,使用相同的构建过程可以构建不同的产品。在软件开发中,如果需要创建复杂对象,并且系统系统具备良好的灵活性和扩展性,可以考虑使用建造者模式。
- 首先建造者模式使得客户端和产品创建的过程解耦,客户端可以不知道产品创建的内部细节;
- 构建过程固定的情况下,构建的细节可以多变性,这就可以很方便的增加不同的构造实现,符合开闭原则;
- 将每个属性的构建过程分解在不同的方法中,精细化管理,高内聚才能低耦合,符合单一职责。
6. 框架中的应用
- StringBuilder和StringBuffer
- SqlSessionBuilder
- Lombok的@Builder注解
相关文章:
【设计模式-6】建造者模式的实现与框架中的应用
建造者模式又被成为生成器模式,是一种使用频率比较低,相对复杂的创建型模式,在很多源码框架中可以看到建造者的使用场景,稍后我们会在本文末尾展示几个框架的使用案例。 建造者模式所构造的对象通常是比较复杂而且庞大的&#x…...
PositiveSSL和Sectigo的多域名证书
首先,我们要知道PositiveSSL是Sectigo旗下的子品牌,提供多种类型的SSL数字证书,包括DV基础型的多域名SSL证书。Sectigo的SSL证书产品同样比较丰富,不仅有DV基础型多域名SSL证书,还有OV企业型以及EV增强型的多域名SSL证…...
Docker:docker exec命令简介
介绍 docker exec [OPTIONS] 容器名称 COMMAND [ARG...] OPTIONS说明: -d,以后台方式执行命令; -e,设置环境变量 -i,交互模式 -t,设置TTY -u,用户名或UID,例如myuser:myu…...
【大数据进阶第三阶段之Hive学习笔记】Hive的数据类型与数据操作
目录 1、Hive数据类型 1.1、基本数据类型 1.2、集合数据类型 1.3、类型转化 2、DDL数据定义 2.1、创建数据库 2.2、查询数据库 2.3删除数据库 2.4、创建表 2.4.1、内部表 2.4.2、外部表 2.4.3管理表与外部表的互相转换 2.5、分区表(partitionÿ…...
GPT2:Language Models are Unsupervised Multitask Learners
目录 一、背景与动机 二、卖点与创新 三、几个问题 四、具体是如何做的 1、更多、优质的数据,更大的模型 2、大数据量,大模型使得zero-shot成为可能 3、使用prompt做下游任务 五、一些资料 一、背景与动机 基于 Transformer 解码器的 GPT-1 证明…...
微创新与稳定性的权衡
之前做过一个项目,业务最高峰CPU使用率也才50%,是一个IO密集型的应用。里面涉及一些业务编排,所以为了提高CPU使用率,我有两个方案:一个是简单的梳理将任务可并行的采用并行流、额外线程池等方式做并行;另外…...
对回调函数的各种讲解说明
有没有跟我师弟一样的童靴~,学习和使用ROS节点时,对其中的callback函数一直摸不着头脑的,以下这么多回调函数的讲解,挨个看,你总会懂的O.o 回调函数怎么调用,如何定义回调函数: 回调函数怎么调用,如何定义…...
Java多线程:创建多线程的三种方式
在Java中,有三种方式创建多线程,继承类Thread,继承接口Runnable,继承接口Callable。其中Thread和Runnable需要重写方法run,方法run没有返回值;Callable需要重写方法call,方法call可以返回值。 …...
Unity中打印信息的两种方式
不继承MonoBehaviour的普通C#类中打印信息: 使用Debug类的方法: Unity提供了Debug类,其中包含了一些用于打印信息的静态方法。以下是常用的几种方法: Debug.Log(message):打印普通信息。Debug.LogWarning(message)&a…...
给定n个字符串s[1...n], 求有多少个数对(i, j), 满足i < j 且 s[i] + s[j] == s[j] + s[i]?
题目 思路: 对于字符串a,b, (a.size() < b.size()), 考虑对字符串b满足什么条件: 由1、3可知a是b的前后缀,由2知b有一个周期是3,即a.size(),所以b是用多个a拼接而成的,有因为a是b的前后缀&…...
Linux磁盘空间与文件大小查看命令详解
1. 查看磁盘空间大小 在Linux系统中,有多个命令可以用来查看磁盘空间的使用情况。最常用的命令是df(disk free)。 df -hdf命令的 -h 选项以人类可读的方式显示磁盘空间,该命令将显示文件系统的使用情况、剩余空间等信息。 2. 查看…...
网络通信过程的一些基础问题
客户端A在和服务器进行TCP/IP通信时,发送和接收数据使用的是同一个端口吗? 这个问题可以这样来思考:在客户端A与服务器B建立连接时,A需要指定一个端口a向服务器发送数据。当服务器接收到A的报文时,从报文头部解析出A的…...
STL——stack容器和queue容器详解
目录 💡stack 💡基本概念 常用接口 💡queue 💡基本概念 💡常用接口 💡stack 💡基本概念 栈(stack):一种特殊的线性表,其只允许在固定的一端…...
django websocket实现聊天室功能
注意事项channel版本 django2.x 需要匹配安装 channels 2 django3.x 需要匹配安装 channels 3 Django3.2.4 channels3.0.3 Django3.2.* channels3.0.2 Django4.2 channles3.0.5 是因为最新版channels默认不带daphne服务器 直接用命令 python manage.py runsever 默认运行的是w…...
软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析
在如今的信息时代,软件已经成为人们日常工作和生活不可或缺的一部分。然而,随着软件的发展和应用范围的不断扩大,软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…...
Go 语言 panic 和 recover 详解
panic() 和 recover() 是 Go 语言中用于处理错误的两个重要函数。panic() 函数用于中止程序并引发panic,而 recover() 函数用于捕获panic并恢复程序的执行。 什么是panic和recover? panic panic() 函数用于中止程序并引发panic。panic() 函数可以接收…...
NAND Separate Command Address (SCA) 接口数据传输解读
在采用Separate Command Address (SCA) 接口的存储产品中,DQ input burst和DQ output burst又是什么样的策略呢? DQ Input Burst: 在读取操作期间,数据以一种快速并行的方式通过DQ总线传送到控制器。在SCA接口下,虽然命令和地址信…...
彻底认识Unity ui设计中Space - Overlay、Screen Space - Camera和World Space三种模式
文章目录 简述Screen Space - Overlay优点缺点 Screen Space - Camera优点缺点 World Space优点缺点 简述 用Unity中开发了很久,但是对unity UI管理中Canvas组件的Render Mode有三种主要类型:Screen Space - Overlay、Screen Space - Camera和World Spa…...
档案数字化怎样快速整理资料
对于机构和组织来说,档案数字化是一个重要的信息管理和保护措施。要快速整理资料进行档案数字化,可以遵循以下步骤: 1. 准备工具和设备:确保有一台计算机、扫描仪和相关软件。 2. 分类和组织资料:先将资料分类…...
面试算法100:三角形中最小路径之和
题目 在一个由数字组成的三角形中,第1行有1个数字,第2行有2个数字,以此类推,第n行有n个数字。例如,下图是一个包含4行数字的三角形。如果每步只能前往下一行中相邻的数字,请计算从三角形顶部到底部的路径经…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
