【Java设计模式】建造者模式 注解@Builder

概念

  • 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它使将一个复杂的对象分解成多个简单的对象,然后一步步构建而成。

  • 每一个具体建造者都相对独立,而与其它的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”。

未用建造者模式

  • 以下举个最简单的例子:电脑配件(包括品牌、价格、描述)、组装电脑。

电脑接口

/*** 电脑接口*/
public interface Computer {/*** 组件(主机Host、显示器Monitor、鼠标Mouse、键盘Keyboard)*/String parts();/*** 品牌*/String brand();/*** 价格*/Double price();/*** 描述*/String desc();
}

主机Host

/*** 惠普主机*/
public class HPHost implements Computer {@Overridepublic String parts() {return "惠普主机";}@Overridepublic String brand() {return "惠普品牌";}@Overridepublic Double price() {return 6999.00;}@Overridepublic String desc() {return "HP Computer Welcome";}
}/*** 联想主机*/
public class LenovoHost implements Computer {@Overridepublic String parts() {return "联想主机";}@Overridepublic String brand() {return "联想品牌";}@Overridepublic Double price() {return 6899.00;}@Overridepublic String desc() {return "Lenovo Computer Welcome";}
}

显示器Monitor

/*** 小米显示器*/
public class RedmiMonitor implements Computer {@Overridepublic String parts() {return "小米显示器";}@Overridepublic String brand() {return "小米品牌";}@Overridepublic Double price() {return 1399.00;}@Overridepublic String desc() {return "Redmi Monitor Welcome";}
}/*** 华硕显示器*/
public class ROGMonitor implements Computer {@Overridepublic String parts() {return "华硕显示器";}@Overridepublic String brand() {return "华硕品牌";}@Overridepublic Double price() {return 1899.00;}@Overridepublic String desc() {return "ROG Monitor Welcome";}
}

鼠标Monse

/*** 罗技鼠标*/
public class GMouse implements Computer {@Overridepublic String parts() {return "罗技鼠标";}@Overridepublic String brand() {return "罗技品牌";}@Overridepublic Double price() {return 139.00;}@Overridepublic String desc() {return "G Mouse Welcome";}
}/*** 联想鼠标*/
public class LenovoMouse implements Computer {@Overridepublic String parts() {return "联想鼠标";}@Overridepublic String brand() {return "联想品牌";}@Overridepublic Double price() {return 89.00;}@Overridepublic String desc() {return "Lenovo Mouse Welcome";}
}

键盘Keyboard

/*** 罗技键盘*/
public class GKeyboard implements Computer {@Overridepublic String parts() {return "罗技键盘";}@Overridepublic String brand() {return "罗技品牌";}@Overridepublic Double price() {return 239.00;}@Overridepublic String desc() {return "G Keyboard Welcome";}
}/*** 惠普键盘*/
public class HPKeyboard implements Computer {@Overridepublic String parts() {return "惠普键盘";}@Overridepublic String brand() {return "惠普品牌";}@Overridepublic Double price() {return 89.00;}@Overridepublic String desc() {return "HP Keyboard Welcome";}
}

组装电脑

*** 组装电脑* 不同的套装配不同的设备*/
public class PackageComputer {/*** 根据套餐数字对应返回整套电脑配置详情** @param choose 套餐数字* @return 电脑配置*/public String getComputer(Integer choose) {// 价格初始值double price;// 组装电脑配件List<Computer> parts = new ArrayList<>();StringBuilder stringBuilder = new StringBuilder();if(choose == 1) {HPHost hpHost = new HPHost();RedmiMonitor redmiMonitor = new RedmiMonitor();LenovoMouse lenovoMouse = new LenovoMouse();HPKeyboard hpKeyboard = new HPKeyboard();// 组装电脑parts.add(hpHost);parts.add(redmiMonitor);parts.add(lenovoMouse);parts.add(hpKeyboard);// 计算价格price = hpHost.price() + redmiMonitor.price() + lenovoMouse.price() + hpKeyboard.price();stringBuilder.append("套餐为:" + choose + "号套餐\r\n");stringBuilder.append("配件如下:\r\n");for(Computer c : parts) {stringBuilder.append(c.parts() + "、");stringBuilder.append(c.brand() + "、");stringBuilder.append(c.price() + "、");stringBuilder.append(c.desc() + "\r\n");}stringBuilder.append("总价格为:" + price + "RMB\r\n");} else if(choose == 2) {LenovoHost lenovoHost = new LenovoHost();ROGMonitor rogMonitor = new ROGMonitor();GMouse gMouse = new GMouse();GKeyboard gKeyboard = new GKeyboard();// 组装电脑parts.add(lenovoHost);parts.add(rogMonitor);parts.add(gMouse);parts.add(gKeyboard);// 计算价格price = lenovoHost.price() + rogMonitor.price() + gMouse.price() + gKeyboard.price();stringBuilder.append("套餐为:" + choose + "号套餐\r\n");stringBuilder.append("配件如下:\r\n");for(Computer c : parts) {stringBuilder.append(c.parts() + "、");stringBuilder.append(c.brand() + "、");stringBuilder.append(c.price() + "、");stringBuilder.append(c.desc() + "\r\n");}stringBuilder.append("总价格为:" + price + "RMB\r\n");} else if(choose == 3) {LenovoHost lenovoHost = new LenovoHost();RedmiMonitor redmiMonitor = new RedmiMonitor();GMouse gMouse = new GMouse();LenovoMouse lenovoMouse = new LenovoMouse();// 组装电脑parts.add(lenovoHost);parts.add(redmiMonitor);parts.add(gMouse);parts.add(lenovoMouse);// 计算价格price = lenovoHost.price() + redmiMonitor.price() + gMouse.price() + lenovoMouse.price();stringBuilder.append("套餐为:" + choose + "号套餐\r\n");stringBuilder.append("配件如下:\r\n");for(Computer c : parts) {stringBuilder.append(c.parts() + "、");stringBuilder.append(c.brand() + "、");stringBuilder.append(c.price() + "、");stringBuilder.append(c.desc() + "\r\n");}stringBuilder.append("总价格为:" + price + "RMB\r\n");}return stringBuilder.toString();}
}

测试

public class BuilderDesign {public static void main(String[] args) {PackageComputer computer = new PackageComputer();System.out.println(computer.getComputer(1));System.out.println("=======================================================");System.out.println(computer.getComputer(2));System.out.println("=======================================================");System.out.println(computer.getComputer(3));}
}

使用建造者模式

  • 从上面可以看出来,电脑的每个配件都要去建对应的类。例子中我给了主机、显示器、鼠标、键盘四种部件,每个部件假设两种品牌,就写了 2 * 4 = 8个类。虽说不会是指数型增长,但是无论哪个增加都会是很明显的增长趋势。而且在组装电脑时,要根据每个不同要求的去返回对应的信息,每一个if语句都有二十行代码左右,看起来十分臃肿。

  • 接下来将会用到建造者模式去优化上面的代码量。

组装电脑接口

public interface IComputer {/*** 主机*/IComputer appendHost(Computer computer);/*** 显示器*/IComputer appendMonitor(Computer computer);/*** 鼠标*/IComputer appendMouse(Computer computer);/*** 键盘*/IComputer appendKeyboard(Computer computer);/*** @return 电脑清单*/String computerDetail();
}

建造者组装电脑

/*** 建造者组装电脑*/
public class BuilderComputer implements IComputer{List<Computer> parts = new ArrayList<>();private double price = 0.00;private Integer choose;public BuilderComputer(){}public BuilderComputer(Integer choose) {this.choose = choose;}@Overridepublic IComputer appendHost(Computer computer) {parts.add(computer);price = price + computer.price();return this;}@Overridepublic IComputer appendMonitor(Computer computer) {parts.add(computer);price = price + computer.price();return this;}@Overridepublic IComputer appendMouse(Computer computer) {parts.add(computer);price = price + computer.price();return this;}@Overridepublic IComputer appendKeyboard(Computer computer) {parts.add(computer);price = price + computer.price();return this;}@Overridepublic String computerDetail() {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("套餐为:" + choose + "号套餐\r\n");stringBuilder.append("配件如下:\r\n");for(Computer c : parts) {stringBuilder.append(c.parts() + "、");stringBuilder.append(c.brand() + "、");stringBuilder.append(c.price() + "、");stringBuilder.append(c.desc() + "\r\n");}stringBuilder.append("总价格为:" + price + "RMB\r\n");return stringBuilder.toString();}
}

建造者

        去掉了繁琐的if else,符合单一职责原则、开闭原则,代码可读性、复用性、拓展性强。这里面就完美的展示了什么叫做将一个复杂对象的构造与它的表示分离。并且链式编程的语法比不断的set()要美观得多,这会在后续Lambok中的@Builder中进行说明。

/*** 建造者*/
public class Builder {/*** @return 一号套餐*/public IComputer chooseOne() {return new BuilderComputer(1).appendHost(new HPHost()).appendMonitor(new RedmiMonitor()).appendMouse(new LenovoMouse()).appendKeyboard(new HPKeyboard());}/*** @return 二号套餐*/public IComputer chooseTwo() {return new BuilderComputer(2).appendHost(new LenovoHost()).appendMonitor(new ROGMonitor()).appendMouse(new GMouse()).appendKeyboard(new GKeyboard());}/*** @return 三号套餐*/public IComputer chooseThree() {return new BuilderComputer(3).appendHost(new LenovoHost()).appendMonitor(new RedmiMonitor()).appendMouse(new GMouse()).appendKeyboard(new LenovoMouse());}
}

测试

public class BuilderDesign {public static void main(String[] args) {Builder builder = new Builder();System.out.println(builder.chooseOne().computerDetail());System.out.println("=======================================================");System.out.println(builder.chooseTwo().computerDetail());System.out.println("=======================================================");System.out.println(builder.chooseThree().computerDetail());}
}

@Builder

        此注解是Lombok依赖下的,而Lombok基本是各个公司都会使用到的工具包。可以用来简化开发。上面的建造者组装电脑的示例代码就是链式编程的关键之处:每个方法除了会传参还会返回this自身。我创建了一个用户User类,其带有六个属性。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {private String username;private String sex;private Integer age;private String address;private String qq;private String email;}

底层

        为了验证此注解背后的样子,最简单的实践方法就是加上此注解然后查看编译后的class文件中的代码。等编译后我发现多了以下内容。会发现多了一个静态内部类UserBuilder以及返回User.UserBuilder的build()方法

        其实User中的builder()方法以及User类的静态内部类UserBuilder的build()方法。这两个方法名在@Builder注解中已经是默认的值了。并且或者注解可以用于类、普通方法和构造方法上。关于其底层是如何在User类中生成静态内部类并且具体的方法代码块就不深究Lombok中的源码了。这里我需要强调的是使用建造者赋值的时候就是赋值给其内部类属性的

优势

可读性好

        其实当使用过@Builder这个注解的时候就已经可以感受到它的好处之一了:美观且可读性高。这里我使用了三种创建对象的方式来作比较出优劣处。

        第一个User对象使用有参构造的真是长的让人反胃,甚至如果在真实的复杂业务场景中,还不知道其中一个参数是什么含义,还需要点进去看注释。并且自己使用这种有参构造的话,如果没有背下来每个位置要放什么参数那就更麻烦了。所以说有参构造的劣势就是:可读性差、参数过多可能导致传递错误。

        第二个User对象就是一直Setter。相比于第三种而言没有那么好的可读性。所以说使用建造者模式的链式编程可读性好。但是要记住建造者模式的赋值是给其内部类属性的

public class BuilderDesign {public static void main(String[] args) {User u1 = new User("张三x", "男", 18, "福建省厦门市xxx镇xxxx小区x楼xxx号", "465795464", "465795464@qq.com");User u2 = new User();u2.setUsername("李四");u2.setSex("女");u2.setAge(20);u2.setAddress("福建省泉州市xxx镇xxxx小区x楼xxx号");u2.setQq("504899214");u2.setEmail("504899214@qq.com");User u3 = User.builder().username("王五").sex("男").age(22).address("福建省福州市xxx镇xxxx小区x楼xxx号").qq("684354768").email("684354768@qq.com").build();}
}

JavaBean创建

        我曾在某个地方看到一个大佬说过使用set()方法注入属性和静态内部类Builder注入属性值的区别,但具体怎么说的已经忘记了,

        这里由衷希望看到这里的读者可以在评论里说一下关于JavaBean赋值可能涉及到的线程安全问题或者其它问题。谢谢。

避坑

        在上面有说过一个问题就是:使用builder()方法赋值是赋值给其静态内部类建造者类的。那么这句话是什么意思呢?这句话的意思就是当我们在实体类上已经附带初始值了,但是使用建造者模式去构建实体类打印toString()方法出来的时候是看到为类加载的初始值的(比如0/false/null等)。具体看以下代码以及控制台输出。

public class BuilderDesign {public static void main(String[] args) {User u = User.builder().username("王五").sex("男").address("福建省福州市xxx镇xxxx小区x楼xxx号").qq("684354768").email("684354768@qq.com").build();System.out.println(u);}
}@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class User {private String username;private String sex;private Integer age = 30;private String address;private String qq;private String email;}

        可以看到age = null。因为age是包装类型Integer,所以类加载时的初始值为null,而不是0。这里的原因就是User的age属性初始值为30,但是其内部的UserBuilder类的age属性并没有,所以导致获取到的User对象的age属性为初始值null。为了避免这个情况发生,@Builder注解中有一个内部注解来解决这个问题,就是@Builder.Default。只需要在设置初始值的属性上使用此注解即可。编译生成的User对象会多生成个静态的$default$age()方法。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {private String username;private String sex;@Builder.Defaultprivate Integer age = 30;private String address;private String qq;private String email;}

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

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

相关文章

nginx 负载均衡

1.环境准备 我使用的说centos7的系统 1.20版本的nginx 另外还有3台虚拟机 主机&#xff1a;192.168.163.142 两台服务器&#xff1a;服务器A--192.168.163.140 服务器B---192.168.163.141 2.配置服务器A和B 找到nginx下的html目录&#xff0c;编辑其中的index.html(在此…

nginx负载均衡

目录 负载均衡 nginx的七层代理和四层代理 四层代理与七层代理之间的区别 四层和七层谁的速度快&#xff1f; 正向代理与反向代理 负载均衡 upstream 算法 算法总结 stream 负载均衡 通过反向代理来实现 nginx的七层代理和四层代理 七层是最常用的反向代理方式&am…

实践指南-前端性能提升 270% | 京东云技术团队

一、背景 当我们疲于开发一个接一个的需求时&#xff0c;很容易忘记去关注网站的性能&#xff0c;到了某一个节点&#xff0c;猛地发现&#xff0c;随着越来越多代码的堆积&#xff0c;网站变得越来越慢。 本文就是从这样的一个背景出发&#xff0c;着手优化网站的前端性能&a…

JS逆向系列之猿人学爬虫第8题-验证码-图文点选

题目地址 https://match.yuanrenxue.cn/match/8本题的难点就在于验证码的识别,没啥js加密,只要识别对了携带坐标就给返回数据 回过头来看验证码 这里复杂的字体比较多,人看起来都有点费劲(感觉可能对红绿色盲朋友不太又好)&#x

PS 2023 安装选项页面显示不全

文章目录 PS 2023 安装选项页面显示不全解决办法 PS 2023 安装选项页面显示不全 解决办法 按住Tab键&#xff0c;点击该安装选项页面即可&#xff0c;如下如所示&#xff1a;

仓储10、20代电子标签接口文档

标签注册 仓储10代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 仓储20代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 查询电子标签信息…

node笔记——调用免费qq的smtp发送html格式邮箱

文章目录 ⭐前言⭐smtp授权码获取⭐nodemailer⭐postman验证接口⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于node调用免费qq的smtp发送邮箱。 node系列往期文章 node_windows环境变量配置 node_npm发布包 linux_配置node node_nvm安装配置 node笔记_h…

小红书攻略:爆款引流,如何在激烈竞争中脱颖而出?

小红书&#xff08;RED&#xff09;作为国内最具影响力的社交电商平台之一&#xff0c;是很多品牌运营者的首选之一。然而&#xff0c;在小红书的激烈竞争中&#xff0c;如何快速引流、吸引关注&#xff0c;成为了品牌运营者面临的挑战。本篇文章一秒推小编将为您介绍小红书运营…

【SpringCloud】Gateway服务网关

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。 1.为什么需要网关…

安全杂记 - 复现nodejs沙箱绕过

目录 一. 配置环境1.下载nodejs2.nodejs配置3.报错解决方法 二. nodej沙箱绕过1. vm模块2.使用this或引用类型来进行沙箱绕过 一. 配置环境 1.下载nodejs 官网&#xff1a;https://nodejs.org/en2.nodejs配置 安装nodejs的msi文件&#xff0c;默认配置一直下一步即可&#x…

Spring 创建和使用

文章目录 创建 Spring 项目1. 创建一个Maven项目2. 添加 Spring 框架支持3. 添加启动类 存储 Bean 对象创建一个 Bean将 Bean 注册到容器 获取并使用 Bean 对象创建 Spring 上下文获取指定的 Bean 对象使用 Bean 对象 getBean() 的用法总结 创建 Spring 项目 1. 创建一个Maven…

山东布谷科技直播软件源码探索高效、稳定直播传输的技术介绍:流媒体传输技术

今天我们探索的是让直播软件源码平台在直播时能够高效、稳定的进行直播传输的技术&#xff0c;而这个技术就是直播软件源码平台的流媒体传输技术&#xff0c;在直播软件源码平台中&#xff0c;流媒体传输技术会将直播的图像、视频、音频等相关的流媒体信号通过网络传递到用户的…

Is a directory: ‘outs//.ipynb_checkpoints‘

提示out/文件夹的.ipynp_chechpoints是一个文件夹&#xff0c;但是打开文件夹却没有看到&#xff0c;可以得知他是一个隐藏文件夹&#xff0c;进入outs/文件夹&#xff0c;使用 ls -a可以看到所有文件 果然出现这个文件夹&#xff0c;但是我们这个outs/文件夹存放的是图片&am…

25分钟极速入门Java基础教程

1. 前置准备及HelloWord案例 1.1. 创建Java项目(本教程采用Idea演示语法) 1.2. 点击上面的创建你会得到下面一些的项目目录 1.3. 编写第一个代码(在控制台输出HelloWord) 1.3.1. 新建一个包&#xff0c;方便我们管理我们的代码 1.3.2. 建立一个类文件,编写代码的地方(也就是我…

TCP三次握手、四次握手过程,以及原因分析

TCP的三次握手和四次挥手实质就是TCP通信的连接和断开。 三次握手&#xff1a;为了对每次发送的数据量进行跟踪与协商&#xff0c;确保数据段的发送和接收同步&#xff0c;根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系&#xff0c;并建立虚连接。 四次挥手&…

Mysql in 查询的奇怪方向

Mysql in 查询的奇怪方向 关于表字段存储的数据为 num1,num2,num3时, 还要通过多个num1,num2入参针对该字段进行查询 建表语句 CREATE TABLE test (test_ids varchar(100) DEFAULT NULL COMMENT 保存ids 以逗号分隔 ) ENGINEInnoDB;数据项 查询语句 SELECT test_ids FROM t…

dotNet 之数据库sqlite

Sqlite3是个特别好的本地数据库&#xff0c;体积小&#xff0c;无需安装&#xff0c;是写小控制台程序最佳数据库。NET Core是同样也是.NET 未来的方向。 **硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 D…

一文详解 DolphinDB SQL 标准化

为了提升用户体验&#xff0c;降低用户学习成本和脚本迁移复杂度&#xff0c;自 1.30.17 / 2.00.5 版本开始&#xff0c;DolphinDB 逐步支持了标准化 SQL 的书写方法&#xff1b;并于 1.30.22 / 2.00.10 版本起&#xff0c;对标准 SQL 的常用语法和关键字实现了兼容。 1. 与标…

HIVE语法优化之Join优化

桶用两表关联字段,MapJoin时需要将小表填入内存,这时候,分桶就起到了作用 一个stage阶段代表一个mr执行,好几个MR,会吧每一个MR的结果都压缩 Mysql 慢查询 如果sql语句执行超过指定时间,定义该sql为慢查询,存储日志, 查问题: SQL日志,模拟慢SQL 然后查询执行计划 分组聚合 就…

2. 软件需求 面向对象分析

目录 1. 软件需求 1.1 需求分类 1.2 需求获取 1.3 需求分析 2. 面向对象分析&#xff08;OOA&#xff09; 2.1 统一建模语言 UML 2.2 用例模型 2.2.1 用例图的元素 2.2.2 识别参与者 2.2.3 合并需求获得用例 2.2.4 细化用例描述 2.3 分析模型 2.3.1 定义概念类 …