设计模式:真正的建造者模式

又臭又长的set方法

经常进行Java项目开发使用各类starter的你一定见过这种代码:

public class SwaggerConfig {@Beanpublic Docket api() {return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build();}
}

相比较你的实体类的set方法,就感觉自己的set直观上就非常的low,十分不优雅,当然这里并不是指实体类,而是类似于一些接口调用时的参数dto对象的构造,在业务代码中构建参数实体十分的麻烦,例如:

@Data
public class DevParam {private Long id;private String filter;private String type;private int pageNo;private int pageSize;
}public class HttpUtil {private final static URL = "http://127.0.0.1:8080/deviceserver/search"public static JSONObject searchDeviceData(DevParam param) {HttpClient client = HttpClient.newHttpClient();HttpRequest request = new HttpRequest();request.uri(URL);request.body(JSONUtil.parseObj(param))//……其他http调用,组装返回数据逻辑、省略return XXX  }
}public class Test{public static void main(String[] args) {//需要调用接口获取数据DevParam param = new DevParam();param.setFilter("关键字");param.setPageNo(1);param.setPageSize(10);JSONObject retData = HttpUtil.searchDeviceData(param);JSONArray array = retData.getJSONArray("data");for(int i=0; i < array.size();i++){//……其他逻辑XXX}}}

而如果写成这样,逼格就会高很多

public class Test{public static void main(String[] args) {//需要调用接口获取数据JSONObject retData = HttpUtil.searchDeviceData(new DevParam().filter("XXXX")  .pageNo(1).pageSize(10));JSONArray array = retData.getJSONArray("data");for(int i=0; i < array.size();i++){//……其他逻辑XXX}}}

实体类的改进:简易版的Builder模式

而上述的DevParam的实现大家应该一眼就能看出来,即将原本void的set方法修改一下就可以满足装X的条件:

public class DevParam {private Long id;private String filter;private String type;private int pageNo;private int pageSize;public DevParam() {}public DevParam setId(Long id) {this.id = id;return this;}public DevParam setFilter(String filter) {this.filter = filter;return this;}public DevParam setType(String type) {this.type = type;return this;}public DevParam setPageNo(int pageNo) {this.pageNo = pageNo;return this;}public DevParam setPageSize(int pageSize) {this.pageSize = pageSize;return this;}}

实体类的改进:基于内部类的Builder实现

如果想看起来跟一开始的swgger的配置类一样高级,则可以使用内部静态类的方式实现该方法,即类内创建一个内部类,通过内部类创建外部类的实体对象。

public class DevParam {private Long id;private String filter;private String type;private int pageNo;private int pageSize;private DevParam() {}//省略此处的get和set方法public static class Builder {private Long id;private String filter;private String type;private int pageNo;private int pageSize;public Builder setId(Long id) {this.id = id;return this;}public Builder setFilter(String filter) {this.filter = filter;return this;}public Builder setType(String type) {this.type = type;return this;}public Builder setPageNo(int pageNo) {this.pageNo = pageNo;return this;}public Builder setPageSize(int pageSize) {this.pageSize = pageSize;return this;}public DevParam build() {DevParam devParam = new DevParam();devParam.id = this.id;devParam.filter = this.filter;devParam.type = this.type;devParam.pageNo = this.pageNo;devParam.pageSize = this.pageSize;return devParam;}}
}public class Test {public static void main(String[] args) {DevParam devParam = new DevParam.Builder().setId(1L).setFilter("filterValue").setType("typeValue").setPageNo(1).setPageSize(10).build();System.out.println(devParam);}
}

该种实现即是我们常用一些配置类、链接类创建时带build小尾巴的方式。

真正意义的建造者模式

其实虽然上述的DevParam类在构建时携带build,但是其实不是真正意义上的建造者模式(Builder),只能说是裁剪版的。建造者模式属于创建型模式,也就生成器模式,其核心思想是:

将复杂对象的构建和表示分离,使同样的构建过程可以创建不同的表示 

建造者模式的结构

Builder:生成器接口,定义创建一个产品(Product)对象所需的各个部件的操作。


Concrete Builder:具体的生成器实现,实现各个部件的创建,并负责组装产品对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。
 

Director:指导者,也被称为导向者,主要用来使用Builder(Builder)接口,以一个统一的过程来构建所需要的Product(Product)对象。
 

Product:产品,表示被生成器构建的复杂对象,包含多个部件。

样例代码:

class Product {private Long id;private String filter;private String type;private int pageNo;private int pageSize;public Product(Long id, String filter, String type, int pageNo, int pageSize) {this.id = id;this.filter = filter;this.type = type;this.pageNo = pageNo;this.pageSize = pageSize;}public Long getId() {return id;}public String getFilter() {return filter;}public String getType() {return type;}public int getPageNo() {return pageNo;}public int getPageSize() {return pageSize;}
}// 构建者接口
interface Builder {void setId(Long id);void setFilter(String filter);void setType(String type);void setPageNo(int pageNo);void setPageSize(int pageSize);Product getResultProduct();
}// 具体构建者
class ConcreteBuilder implements Builder {private Long id;private String filter;private String type;private int pageNo;private int pageSize;@Overridepublic void setId(Long id) {this.id = id;}@Overridepublic void setFilter(String filter) {this.filter = filter;}@Overridepublic void setType(String type) {this.type = type;}@Overridepublic void setPageNo(int pageNo) {this.pageNo = pageNo;}@Overridepublic void setPageSize(int pageSize) {this.pageSize = pageSize;}@Overridepublic Product getResultProduct() {return new Product(id, filter, type, pageNo, pageSize);}
}// 指挥者
class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}public void construct() {builder.setId(1L);builder.setFilter("filterValue");builder.setType("typeValue");builder.setPageNo(1);builder.setPageSize(10);}public Product getResultProduct() {return builder.getResultProduct();}
}public class Main {public static void main(String[] args) {Director director = new Director(new ConcreteBuilder());director.construct();Product product = director.getResultProduct();System.out.println(product);}
}

一个建造者经典案例

可能仅看上述建造者的demo代码,发现还不如简易版或者内部类版本的创建者,甚至都不如单纯的实体类set省事,但是先别急,我们带入一个实际的场景来看,以一个导出数据的案例来看,在做导出功能时,通常需要支持导出不同格式的数据文件,以此来做一个文件导出框架,通常对于导出框架,需要对导出内容和格式进行一个整体的约束:

  1. 导出的文件,不管什么格式,都分成3个部分,分别是文件头、文件体和文件尾。
  2. 在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔。
  3. 在文件体部分,需要描述如下信息:表名称,然后分条描述数据。对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔。
  4. 在文件尾部分,需要描述如下信息:输出人。

下面对以上需求基于建造者进行设计类图:

具体代码实现:

// 抽象构建者接口
interface Builder {void buildHeader();void buildBody();void buildFooter();StringBuffer getResult();
}// 具体构建者:XML格式
class XmlBuilder implements Builder {private StringBuffer buffer;public XmlBuilder() {this.buffer = new StringBuffer();}@Overridepublic void buildHeader() {buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");buffer.append("<document>\n");}@Overridepublic void buildBody() {buffer.append("    <body>\n");buffer.append("        <content>Example content</content>\n");buffer.append("    </body>\n");}@Overridepublic void buildFooter() {buffer.append("</document>\n");}@Overridepublic StringBuffer getResult() {return buffer;}
}// 具体构建者:TXT格式
class TxtBuilder implements Builder {private StringBuffer buffer;public TxtBuilder() {this.buffer = new StringBuffer();}@Overridepublic void buildHeader() {buffer.append("Document Content:\n");}@Overridepublic void buildBody() {buffer.append("Example content\n");}@Overridepublic void buildFooter() {buffer.append("End of Document\n");}@Overridepublic StringBuffer getResult() {return buffer;}
}// 指挥者
class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}public void construct() {builder.buildHeader();builder.buildBody();builder.buildFooter();}public StringBuffer getResult() {return builder.getResult();}
}// 客户端使用示例
public class BuilderPatternDemo {public static void main(String[] args) {Director director;// 创建XML格式的构建者director = new Director(new XmlBuilder());director.construct();System.out.println("XML Format:\n" + director.getResult());// 创建TXT格式的构建者director = new Director(new TxtBuilder());director.construct();System.out.println("TXT Format:\n" + director.getResult());}
}

首先通过builder定义了构建文档所需的方法buildHeaderbuildBodybuildFooter 和 getResult,以两个类分别根据构建的文件特征实现了Builder接口;最后通过指导者使用Builder来构建文档,并在构建完成后获取最终的文档内容。

建造者模式的理解

建造者模式的主要功能是构建复杂的产品,而且是细化的、分步骤的构建产品,也就是建造者模式重在一步一步解决构造复杂对象的问题。如果仅仅这么认识建造者模式的功能是不够的。更为重要的是,这个构建的过程是统一的、固定不变的,变化的部分放到生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。

再直白点说,建造者模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。

建造者模式的构成

要特别注意,建造者模式分成两个很重要的部分。

  1. 一个部分是 Builder 接口,这里是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;
  2. 另外一个部分是 Director,Director 是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤地来执行。

不管如何变化,Builder 模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在生成器模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式,所以要严格区分这两个部分。

在指导者(Director)实现整体构建算法时,遇到需要创建和组合的具体部件的时候,就会把这些功能通过委托,交给Builder去完成。

建造者模式的本质

建造者模式的本质是分离整体构建算法和部件构造。构建一个复杂的对象,本来就有构建的过程,以及构建过程中具体的实现。建造者模式就是用来分离这两个部分,从而使得程序结构更松散、扩展更容易、复用性更好,同时也会使得代码更清晰,意图更明确。
虽然在建造者模式的整体构建算法中,会一步一步引导Builder来构建对象,但这并不是说生成器主要就是用来实现分步骤构建对象的。建造者模式的重心还是在于分离整体构建算法和部件构造,而分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物。

何时使用建造者模式

  • 如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
  • 如果同一个构建过程有着不同的表示时(例如导出案例)。

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

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

相关文章

【BUG】已解决:ModuleNotFoundError: No module named ‘cv2’

已解决&#xff1a;ModuleNotFoundError: No module named ‘cv2’ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开…

基于 Web 的家校联系系统的设计与实现

目录 基于 Web 的家校联系系统的设计与实现 一、绪论 &#xff08;一&#xff09;研究背景 &#xff08;二&#xff09; 研究目的 &#xff08;三&#xff09; 研究意义 二、需求分析 &#xff08;一&#xff09; 功能需求 &#xff08;二&#xff09; 性能需求 &#…

开发一个自己的chrom插件

开发一个自己的chrom插件 一、创建一个文件夹 二、配置文件manifest.json 创建名字为&#xff1a;manifest.json的配置文件&#xff0c;模板如下&#xff1a; {"manifest_version": 3,"name": "Hello World Extension","version": …

AV1 编码标准屏幕内容编码技术概述

AV1 屏幕内容编码 为了提高屏幕捕获内容的压缩性能&#xff0c;AV1采用了几种编码工具&#xff0c;例如用于处理屏幕画面中重复模式的内帧内块复制&#xff08;IntraBC&#xff09;&#xff0c;以及用于处理颜色数量有限的屏幕块的调色板模式。 帧内块拷贝 AV1 编码中的 Intra …

【Elasticsearch7.11】reindex问题

参考博文链接 问题&#xff1a;reindex 时出现如下问题 原因&#xff1a;数据量大&#xff0c;kibana的问题 解决方法&#xff1a; 将DSL命令转化成CURL命令在服务上执行 CURL命令 自动转化 curl -XPOST "http://IP:PORT/_reindex" -H Content-Type: application…

Python: 一些python和Java不同的基础语法

文章目录 1. 数据类型2. 字符串的引用3. 字符串拼接4. Python中的报错5. Python中的输入语句(input)6. 运算符(**和//)7. 除法运算8. 注释方法: #或者三引号9. Python中的比较10. Java中用and, or, not代替逻辑运算符11. 多元赋值12. Python不支持自增自减操作13. 在Python中, …

zookeeper基础知识学习

官网&#xff1a;Apache ZooKeeper 下载地址&#xff1a;Index of /dist/zookeeper/zookeeper-3.5.7Index of /dist/zookeeperIndex of /dist/zookeeper/zookeeper-3.5.7 ZK配置参数说明&#xff1a; 1、tickTime2000&#xff1a;通讯心跳时间&#xff0c;zookeeper服务器与客…

笑中带泪《抓娃娃》

【标题】笑中带泪&#xff0c;《抓娃娃》&#xff1a;一场关于成长的中式惊悚喜剧盛宴在这个夏日炎炎的季节里&#xff0c;一部名为《抓娃娃》的电影悄然上映&#xff0c;以沈腾与马丽的黄金搭档再次点燃观众的热情。然而&#xff0c;当笑声在影院回荡时&#xff0c;一股不易察…

搭建图片缓存服务器,解决图片访问403 Forbidden问题

在现代Web开发中&#xff0c;图片是网站和应用的重要组成部分。然而&#xff0c;有时我们在访问某些图片时会遇到403 Forbidden错误&#xff0c;尤其是自己的应用访问互联网上的三方的图片时&#xff0c;这通常是由于别人的服务器设置了访问限制。本文将介绍如何通过搭建一个图…

7月15日学习打卡,二叉搜索树和字符串操作

hello大家好呀&#xff0c;本博客目的在于记录暑假学习打卡&#xff0c;后续会整理成一个专栏&#xff0c;主要打算在暑假学习完数据结构&#xff0c;因此会发一些相关的数据结构实现的博客和一些刷的题&#xff0c;个人学习使用&#xff0c;也希望大家多多支持&#xff0c;有不…

C#学习

C#学习 1.B站丑萌气质狗C#的循环-判断泛型错误处理面向对象static的使用定义showInfo类和Hero类 在这里插入图片描述 然后在该解决方案add新建一个类库&#xff0c;点击rebuild&#xff0c;会在bin文件夹下生成.dll文件 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direc…

数据类型与运算符

一、进制 1.1 进制的区分 1.2进制的换算 只要掌握计算器的方式即可。人工计算的方式了解一下就行了。 二、计算机数据的存储原理 2.1 存储单位 最小的单位&#xff1a;比特位 bit 1位比特要么为0&#xff0c;要么为1 最基本的单位&#xff1a;字节 …

Spring-Cache 缓存

1.简介 2.SpringCache 整合 简化缓存开发 1.导入依赖 <!-- spring cache --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>2.redis 作为缓存…

二分法binary search

欢迎来到一夜看尽长安花 博客&#xff0c;您的点赞和收藏是我持续发文的动力 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何想要讨论的问题可联系我&#xff1a;3329759426qq.com 。发布文章的风格因专栏而异&#xff0c;均自成体系&#xff0c;不足…

解决一下git clone失败的问题

1&#xff09;.不开梯子&#xff0c;我们用https克隆 git clone https://github.com 报错&#xff1a; Failed to connect to github.com port 443 after 2091 ms: Couldnt connect to server 解决办法&#xff1a; 开梯子&#xff0c;然后# 注意修改成自己的IP和端口号 gi…

docker搭建普罗米修斯监控gpu

ip8的服务器监控ip110和ip111的服务器 被监控的服务器110和111只需要安装node-export和nvidia-container-toolkit 下载镜像包 docker pull prom/node-exporter docker pull prom/prometheus docker pull grafana/grafana新建目录 mkdir /opt/prometheus cd /opt/prometheus/…

生信软件27 - 基于python的基因注释数据查询/检索库mygene

1. mygene库简介 MyGene.info提供简单易用的REST Web服务来查询/检索基因注释数据&#xff0c;具有以下特点&#xff1a; mygene技术文档&#xff1a; https://docs.mygene.info/en/latest/ 多物种支持: 包括人、小鼠、大鼠、斑马鱼等多个模式生物&#xff1b; 多数据源聚合…

卷积神经网络图像识别车辆类型

卷积神经网络图像识别车辆类型 1、图像 自行车: 汽车: 摩托车: 2、数据集目录 3、流程 1、获取数据,把图像转成矩阵,并随机划分训练集、测试集 2、把标签转为数值,将标签向量转换为二值矩阵 3、图像数据归一化,0-1之间的值 4、构造卷积神经网络 5、设置图像输入…

记录些MySQL题集(8)

ACID原则、事务隔离级别及事务机制原理 一、事务的ACID原则 什么是事务呢&#xff1f;事务通常是由一个或一组SQL组成的&#xff0c;组成一个事务的SQL一般都是一个业务操作&#xff0c;例如聊到的下单&#xff1a;「扣库存数量、增加订单详情记录、插入物流信息」&#xff0…

Qt5.12.2安装教程

文章目录 文章介绍下载连接安装教程 文章介绍 安装Qt5.12.2 下载连接 点击官网下载 安装包下载完毕 安装教程 点开设置&#xff0c;添加临时储存库&#xff0c;复制连接“https://download.qt.io/online/qtsdkrepository/windows_x86/root/qt/” 点击测试&#xff0…