当前位置: 首页 > news >正文

【设计模式-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】建造者模式的实现与框架中的应用

建造者模式又被成为生成器模式&#xff0c;是一种使用频率比较低&#xff0c;相对复杂的创建型模式&#xff0c;在很多源码框架中可以看到建造者的使用场景&#xff0c;稍后我们会在本文末尾展示几个框架的使用案例。  建造者模式所构造的对象通常是比较复杂而且庞大的&#x…...

PositiveSSL和Sectigo的多域名证书

首先&#xff0c;我们要知道PositiveSSL是Sectigo旗下的子品牌&#xff0c;提供多种类型的SSL数字证书&#xff0c;包括DV基础型的多域名SSL证书。Sectigo的SSL证书产品同样比较丰富&#xff0c;不仅有DV基础型多域名SSL证书&#xff0c;还有OV企业型以及EV增强型的多域名SSL证…...

Docker:docker exec命令简介

介绍 docker exec [OPTIONS] 容器名称 COMMAND [ARG...] OPTIONS说明&#xff1a; -d&#xff0c;以后台方式执行命令&#xff1b; -e&#xff0c;设置环境变量 -i&#xff0c;交互模式 -t&#xff0c;设置TTY -u&#xff0c;用户名或UID&#xff0c;例如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、分区表&#xff08;partition&#xff…...

GPT2:Language Models are Unsupervised Multitask Learners

目录 一、背景与动机 二、卖点与创新 三、几个问题 四、具体是如何做的 1、更多、优质的数据&#xff0c;更大的模型 2、大数据量&#xff0c;大模型使得zero-shot成为可能 3、使用prompt做下游任务 五、一些资料 一、背景与动机 基于 Transformer 解码器的 GPT-1 证明…...

微创新与稳定性的权衡

之前做过一个项目&#xff0c;业务最高峰CPU使用率也才50%&#xff0c;是一个IO密集型的应用。里面涉及一些业务编排&#xff0c;所以为了提高CPU使用率&#xff0c;我有两个方案&#xff1a;一个是简单的梳理将任务可并行的采用并行流、额外线程池等方式做并行&#xff1b;另外…...

对回调函数的各种讲解说明

有没有跟我师弟一样的童靴~&#xff0c;学习和使用ROS节点时&#xff0c;对其中的callback函数一直摸不着头脑的&#xff0c;以下这么多回调函数的讲解&#xff0c;挨个看&#xff0c;你总会懂的O.o 回调函数怎么调用,如何定义回调函数&#xff1a; 回调函数怎么调用,如何定义…...

Java多线程:创建多线程的三种方式

在Java中&#xff0c;有三种方式创建多线程&#xff0c;继承类Thread&#xff0c;继承接口Runnable&#xff0c;继承接口Callable。其中Thread和Runnable需要重写方法run&#xff0c;方法run没有返回值&#xff1b;Callable需要重写方法call&#xff0c;方法call可以返回值。 …...

Unity中打印信息的两种方式

不继承MonoBehaviour的普通C#类中打印信息&#xff1a; 使用Debug类的方法&#xff1a; Unity提供了Debug类&#xff0c;其中包含了一些用于打印信息的静态方法。以下是常用的几种方法&#xff1a; Debug.Log(message)&#xff1a;打印普通信息。Debug.LogWarning(message)&a…...

给定n个字符串s[1...n], 求有多少个数对(i, j), 满足i < j 且 s[i] + s[j] == s[j] + s[i]?

题目 思路&#xff1a; 对于字符串a&#xff0c;b, (a.size() < b.size()), 考虑对字符串b满足什么条件&#xff1a; 由1、3可知a是b的前后缀&#xff0c;由2知b有一个周期是3&#xff0c;即a.size()&#xff0c;所以b是用多个a拼接而成的&#xff0c;有因为a是b的前后缀&…...

Linux磁盘空间与文件大小查看命令详解

1. 查看磁盘空间大小 在Linux系统中&#xff0c;有多个命令可以用来查看磁盘空间的使用情况。最常用的命令是df&#xff08;disk free&#xff09;。 df -hdf命令的 -h 选项以人类可读的方式显示磁盘空间&#xff0c;该命令将显示文件系统的使用情况、剩余空间等信息。 2. 查看…...

网络通信过程的一些基础问题

客户端A在和服务器进行TCP/IP通信时&#xff0c;发送和接收数据使用的是同一个端口吗&#xff1f; 这个问题可以这样来思考&#xff1a;在客户端A与服务器B建立连接时&#xff0c;A需要指定一个端口a向服务器发送数据。当服务器接收到A的报文时&#xff0c;从报文头部解析出A的…...

STL——stack容器和queue容器详解

目录 &#x1f4a1;stack &#x1f4a1;基本概念 常用接口 &#x1f4a1;queue &#x1f4a1;基本概念 &#x1f4a1;常用接口 &#x1f4a1;stack &#x1f4a1;基本概念 栈&#xff08;stack&#xff09;&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端…...

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…...

软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析

在如今的信息时代&#xff0c;软件已经成为人们日常工作和生活不可或缺的一部分。然而&#xff0c;随着软件的发展和应用范围的不断扩大&#xff0c;软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…...

Go 语言 panic 和 recover 详解

panic() 和 recover() 是 Go 语言中用于处理错误的两个重要函数。panic() 函数用于中止程序并引发panic&#xff0c;而 recover() 函数用于捕获panic并恢复程序的执行。 什么是panic和recover&#xff1f; panic panic() 函数用于中止程序并引发panic。panic() 函数可以接收…...

NAND Separate Command Address (SCA) 接口数据传输解读

在采用Separate Command Address (SCA) 接口的存储产品中&#xff0c;DQ input burst和DQ output burst又是什么样的策略呢&#xff1f; DQ Input Burst: 在读取操作期间&#xff0c;数据以一种快速并行的方式通过DQ总线传送到控制器。在SCA接口下&#xff0c;虽然命令和地址信…...

彻底认识Unity ui设计中Space - Overlay、Screen Space - Camera和World Space三种模式

文章目录 简述Screen Space - Overlay优点缺点 Screen Space - Camera优点缺点 World Space优点缺点 简述 用Unity中开发了很久&#xff0c;但是对unity UI管理中Canvas组件的Render Mode有三种主要类型&#xff1a;Screen Space - Overlay、Screen Space - Camera和World Spa…...

档案数字化怎样快速整理资料

对于机构和组织来说&#xff0c;档案数字化是一个重要的信息管理和保护措施。要快速整理资料进行档案数字化&#xff0c;可以遵循以下步骤&#xff1a; 1. 准备工具和设备&#xff1a;确保有一台计算机、扫描仪和相关软件。 2. 分类和组织资料&#xff1a;先将资料分类&#xf…...

面试算法100:三角形中最小路径之和

题目 在一个由数字组成的三角形中&#xff0c;第1行有1个数字&#xff0c;第2行有2个数字&#xff0c;以此类推&#xff0c;第n行有n个数字。例如&#xff0c;下图是一个包含4行数字的三角形。如果每步只能前往下一行中相邻的数字&#xff0c;请计算从三角形顶部到底部的路径经…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...