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

又臭又长的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(Map<String, Collection<ExportDataModel>> mapData);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(Map<String, Collection<ExportDataModel>> mapData) {buffer.append("<Body>\n");for (String tblName : mapData.keySet()) {// 先拼接表名称buffer.append("\n<Datas TableName=\"" + tblName + "\">\n");// 然后循环拼接具体数据for (ExportDataModel edm : mapData.get(tblName)) {buffer.append("\n<Data>\n");buffer.append("\n<ProductId>" + edm.getProductId() + "</ProductId>\n");buffer.append("\n<Price>" + edm.getPrice() + "</Price>\n");buffer.append("\n<Amount>" + edm.getAmount() + "</Amount>\n");buffer.append("\n</Data>\n");}buffer.append("\n</Datas>\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(Map<String, Collection<ExportDataModel>> mapData) {for (String tblName : mapData.keySet()) {// 先拼接表名称buffer.append(tblName + "\n");// 然后循环拼接具体数据for (ExportDataModel edm : mapData.get(tblName)) {buffer.append(edm.getProductid() + ","+ edm.getPrice() + ","+ edm.getAmount() + "\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://xiahunao.cn/news/3247881.html

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

相关文章

解决VMware虚拟机在桥接模式下无法上网的问题

解决VMware虚拟机在桥接模式下无法上网的问题 windows11系统自动启动了热点功能&#xff0c;开启热点可能会干扰虚拟机的桥接设置。 方法一&#xff1a;windows11可以提供网络热点服务 方法二&#xff1a;手动指定桥接的物理网卡 方法一&#xff1a;关闭热点功能 优点&#xff…

少儿编程启蒙宝典:Scratch动画游戏108变

一、编程教育的时代价值与意义 随着数字时代的深入发展&#xff0c;社会对人才的需求正发生深刻变革&#xff0c;计算思维与编程能力已成为衡量个人竞争力的重要指标。在此背景下&#xff0c;培养孩子们运用计算思维解决实际问题的能力&#xff0c;成为教育领域的重要任务。编…

运动用什么骨传导耳机好?推荐这五款运动骨传导耳机!

在运动生涯&#xff0c;我见证了自我挑战与超越的每一个瞬间&#xff0c;而这一切都离不开那如影随形的运动骨传导耳机。一款出色的运动耳机&#xff0c;其重要性不言而喻——它不仅是提升运动效率的得力助手&#xff0c;更是开启多元化运动体验的金钥匙。近年来&#xff0c;运…

网络结构-组件-AI(九)

深度学习网络组件 RNN公式讲解计算示意图讲解 CNN计算示意 Normalization(归一化层)Normalization常见两种方式 Dropout层 RNN 循环神经网络&#xff08;recurrent neural network&#xff09; 主要思想&#xff1a; 即将整个序列划分成多个时间步&#xff0c;将每一个时间步的…

创建通用JS公共模块并发布至npm

title: 创建通用JS公共模块并发布至npm tags: UMD rollup verdaccio npm categories: 模块化 概要内容 创建&#xff1a;JS公共模块 打包&#xff1a;使用rollup 打包公共模块 发布&#xff1a;js公共模块至verdaccio平台 发布&#xff1a;js公共模块至npm平台 如何创建JS公共模…

媒体邀约宣传做了13年,我们总结了哪些经验?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 「51媒体」作为一家在媒体邀约宣传领域深耕13年的专业机构&#xff0c;积累了一些经验。现在与大家分享下&#xff1a; 合理的制定媒体邀约传播方案 在进行媒体邀约前&#xff0c;首先需…

木舟0基础学习Java的第二十天(线程,实现,匿名有名,休眠,守护,加入,设计,计时器,通信)

多线程 并发执行的技术 并发和并行 并发&#xff1a;同一时间 有多个指令 在单个CPU上 交替执行 并行&#xff1a;同一时间 有多个指令 在多个CPU上 执行 进程和线程 进程&#xff1a;独立运行 任何进程 都可以同其他进程一起 并发执行 线程&#xff1a;是进程中的单个顺…

【人工智能】深度剖析AI伦理:强化隐私防线,推动算法公平性的核心议题

文章目录 &#x1f34a;1 人工智能兴起背后的伦理及道德风险1.1 算法偏见与歧视1.2 数据隐私侵权1.3 透明度受限1.4 决策失衡1.5 AI生成内容的危险性 &#x1f34a;2 建构AIGC伦理观&#xff1a;实现人机共创的永续提升2.1 技术手段与伦理预防2.2 即时警告与紧急关停措施2.3 法…

图片如何去水印,PS 图片去水印的几种常见方法

在数字图像的世界里&#xff0c;水印常常被用来标识版权或防止未经授权的使用&#xff0c;但有时它们却成为了美观的障碍。无论是出于个人偏好还是专业需求&#xff0c;去除图片上的水印已经成为一项常见的任务。 Adobe Photoshop 作为行业标准的图像编辑软件&#xff0c;提供…

队列(Queue),循环队列,双端队列(Deque)and LeetCode刷题

队列&#xff08;Queue&#xff09;&#xff0c;循环队列&#xff0c;双端队列&#xff08;Deque&#xff09;and LeetCode刷题 1. 队列的概念2.队列的使用3. 队列的模拟实现3.1 用链式结构实现队列3.2 用顺序结构实现队列 4. 循环队列5. 双端队列&#xff08;Deque&#xff09…

【内网安全】横向移动-Wmi-Smb-CME密码喷射

目录 环境介绍域信息收集-横向移动前置判断是不是在域内获取域控主机的内网ip端口扫描内网获取主机密码 域横向移动-WMI-自带&命令&套件&插件1.wmic系统自带&#xff1a;(单执行&#xff1a;即无回显) 2.cscript系统自带&#xff1a;(交互式) 3.wmiexec-impacket&a…

文献阅读:A Case for Managed and Model-less Inference Serving

目录 知识点记录推理服务在线推理特点 动机&#xff1a;为什么作者想要解决这个问题&#xff1f;贡献&#xff1a;作者在这篇论文中完成了什么工作(创新点)&#xff1f;规划&#xff1a;他们如何完成工作&#xff1f;1.挑战1.1 选择一个模型变体1.2 异构硬件1.3 资源提供1.4 启…

MySQL双主双从实现方式

双主双从&#xff08;MM-SS&#xff09; 前言 避免单一主服务器宕机&#xff0c;集群写入能力缺失 从 1 复制 主1 &#xff0c;从 2 复制 主 2 主 1 复制 主 2&#xff0c;主 2 复制主 1 也就是 主 1 和主 2 互为主从。主1主2互为主从&#xff0c; 是为了以下情景&#xff0c…

初识XXE漏洞及ctfshow做题(373-378)

初识XXE漏洞 1.XXE简介 XXE就是XML外部实体注入&#xff0c;当允许引用外部实体时&#xff0c; XML数据在传输中有可能会被不法分子被修改&#xff0c;如果服务器执行被恶意插入的代码&#xff0c;就可以实现攻击的目的攻击者可以通过构造恶意内容&#xff0c;就可能导致任意…

昇思25天学习打卡营第29天 | 文本解码原理--以MindNLP为例

今天是29天&#xff0c;学习了文本解码原理--以MindNLP为例。 MindNLP 是一个基于 MindSpore 的开源自然语言处理&#xff08;NLP&#xff09;库。它具有以下特点&#xff1a; 支持多种 NLP 任务&#xff1a;如语言模型、机器翻译、问答、情感分析、序列标记、摘要等&#xff…

SPINDILOMETER:用于多导睡眠图的睡眠纺锤波模型

摘要 通过对近年来睡眠脑电(EEG)信号分析方法的研究&#xff0c;本文提出了一种可集成到多导睡眠图(PSG)设备中的SPINDILOMETER模型&#xff0c;以供PSG电生理信号研究人员、临床睡眠医生和技术人员使用。为此&#xff0c;通过分析PSG中的脑电信号&#xff0c;开发了一个测量睡…

算法题目整合

文章目录 121. 小红的区间翻转142. 两个字符串的最小 ASCII 删除总和143. 最长同值路径139.完美数140. 可爱串141. 好二叉树 121. 小红的区间翻转 小红拿到了两个长度为 n 的数组 a 和 b&#xff0c;她仅可以执行一次以下翻转操作&#xff1a;选择a数组中的一个区间[i, j]&…

【Neural signal processing and analysis zero to hero】- 2

Nonstationarities and effects of the FT course from youtube: 传送地址 why we need extinguish stationary and non-stationary signal, because most of neural signal is non-stationary. Welch’s method for smooth spectral decomposition Full FFT method y…

用Docker来开发

未完成。。。 现在好像用Docker是越来越多了。之前其实也看过docker的原理&#xff0c;大概就是cgroup那些&#xff0c;不过现在就不看原理了&#xff0c;不谈理论&#xff0c;只看实际中怎么用&#xff0c;解决眼前问题。 用docker来做开发&#xff0c;其实就是解决的编译环境…

ArkUI-动画

属性动画 属性动画是通过设置组建的animation属性来给组件添加动画&#xff0c;当组件的width、height、Opacity、backgroundColor、scale、rotate、translate等属性变更时&#xff0c;可以实现渐变过渡效果 Text().position({x: 10, //x轴坐标y: 0 //y轴坐标}).rotate…