【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.rhkb.cn/news/79403.html

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

相关文章

JavaWeb 手写Tomcat底层机制

目录 一、Tomcat底层整体架构 1.简介 : 2.分析图 : 3.基于Socket开发服务端的流程 : 4.打通服务器端和客户端的数据通道 : 二、多线程模型的实现 1.思路分析 : 2.处理HTTP请求 : 3.自定义Tomcat : 三、自定义Servlet规范 1. HTTP请求和响应 : 1 CyanServletRequest …

布隆过滤器,Guava实现布隆过滤器(本地内存),Redis实现布隆过滤器(分布式)

一、前言 利用布隆过滤器可以快速地解决项目中一些比较棘手的问题。如网页 URL 去重、垃圾邮件识别、大集合中重复元素的判断和缓存穿透等问题。不知道从什么时候开始&#xff0c;本来默默无闻的布隆过滤器一下子名声大噪&#xff0c;在面试中面试官问到怎么避免缓存穿透&#…

Multimodal Learning with Transformer: A Survey

Transformer多模态学习 Abstract1 INTRODUCTION2 BACKGROUND2.1 Multimodal Learning (MML)2.2 Transformers: a Brief History and Milestones2.3 Multimodal Big Data 3 TRANSFORMERS: A GEOMETRICALLY TOPOLOGICAL PERSPECTIVE3.1 Vanilla Transformer3.1.1 Input Tokenizat…

如何免费申请SSL证书

如何免费申请SSL证书 文章目录 如何免费申请SSL证书前言1. 向域名平台申请SSL证书1.1 购买“免费证书” 2. 进一步进行创建证书设置2.1 对证书的关联域名进行补充 3. 云解析DNS3.1 进行验证信息 前言 我们可以成功地将自己购买的域名&#xff0c;绑定到连接本地群晖NAS的数据隧…

【LeetCode每日一题】——85.最大矩形

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 困难 三【题目编号】 85.最大矩形 四【题目描述】 给定一个仅包含 0 …

Yolov8-pose关键点检测:模型轻量化设计 | 引入Ghostnet、G_ghost、Ghostnetv2、repghost,进行性能对比

💡💡💡本文解决什么问题:Yolov8-pose关键点评估不同轻量级网络的性能,引入Ghostnet、G_ghost、Ghostnetv2、repghost等网络进行可行性分析 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.net/m0_63774211/category_12398833.html ✨✨✨手把手教你从数据标记到生…

Python(Web时代)——请求钩子

简介 有时在处理请求之前或之后需要执行一部分代码&#xff0c;比如&#xff1a;创建数据库链接或进行登陆权限认证等&#xff0c;在请求结束时指定数据的交互格式等。 为了避免在每个视图函数中编写重复的代码&#xff0c;flask提供了注册通用函数的功能&#xff08;请求钩子…

windows永久暂停更新

目录 1.winr,输入regedit打开注册表 2.打开注册表的这个路径: 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键空白地方新建QWORD值命名为:FlightSettingsMaxPauseDays 3.双击FlightSettingsMaxPauseDays,修改里面的值为100000,右边基数设置…

17款奔驰S400升级原厂前排座椅通风系统,夏天必备的功能

通风座椅的主动通风功能可以迅速将座椅表面温度降至适宜程度&#xff0c;从而确保最佳座椅舒适性。该功能启用后&#xff0c;车内空气透过打孔皮饰座套被吸入座椅内部&#xff0c;持续时间为 8 分钟。然后&#xff0c;风扇会自动改变旋转方向&#xff0c;将更凉爽的环境空气从座…

Total Variation loss

Total Variation loss 适合任务 图像复原、去噪等 处理的问题 图像上的一点点噪声可能就会对复原的结果产生非常大的影响&#xff0c;很多复原算法都会放大噪声。因此需要在最优化问题的模型中添加一些正则项来保持图像的光滑性&#xff0c;图片中相邻像素值的差异可以通过…

python编写ocr识别图片汉字

当你需要构建一个简单的图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;并在其中实现光学字符识别&#xff08;OCR&#xff09;功能时&#xff0c;wxPython是一个强大而灵活的选择。wxPython是一个基于Python的跨平台GUI开发框架&#xff0c;结合了wxWidgets C库…

微信提示操作太频繁怎么办?

微信使用过程中&#xff0c;遇到“操作太频繁”的提示该怎么办&#xff1f; 而提示频繁最常见的两种情况&#xff1a;加好友频繁和发消息频繁。 微信为什么提示频繁&#xff1f;频繁是因为微信故意这样设置的&#xff0c;压根就不想你群发&#xff01;&#xff01;&#xff0…

黑马程序员SpringMVC练手项目

目录 1、需求 2、项目准备 pom.xml SQL jdbc.properties log4j.properties applicationContext.xml spring-mvc.xml web.xml 3、工作流程 4、难点 项目已经上传到gitee&#xff1a;https://gitee.com/xzl-it/my-projects 1、需求 SpringMVC项目练习&#xff1a;数…

bagging集成与boosting集成的区别是什么?

bagging集成与boosting集成的区别 区别一:数据方面 Bagging&#xff1a;对数据进行采样训练; Boosting&#xff1a;根据前一轮学习结果调整数据的重要性。 区别二:投票方面 Bagging&#xff1a;所有学习器平权投票; Boosting&#xff1a;对学习器进行加权投票。 区别三:…

全志D1-H (MQ-Pro)驱动 OV5640 摄像头

内核配置 运行 m kernel_menuconfig 勾选下列驱动 Device Drivers ---><*> Multimedia support --->[*] V4L platform devices ---><*> Video Multiplexer[*] SUNXI platform devices ---><*> sunxi video input (camera csi/mipi…

Android:自己写一个简单记事本

一、前言&#xff1a;我的app是点击加号跳转到另一个界面 那么我遇到的问题的是点击加号是一个从一个Fragment跳转到另一个Fragment跳转失败。 二、解决方案&#xff1a; //相应控件的监听里面实现跳转FragmentManager fragmentManagergetFragmentManager();fragmentManager.b…

react ant icon的简单使用

refer: 快速上手 - Ant Design 1.引入ant npm install antd --save 2.在页面引用&#xff1a; import { StarOutlined } from ant-design/icons; 如果想要引入多个icon&#xff0c;可以这样书写&#xff1a; import { UserOutlined, MailOutlined, PieChartOutlined } fr…

GG修改美食大战老鼠宝石等级以及修改其他资料的方法

这期主要是讲一些&#xff0c;大家修改遇到的问题以及修改其他参数。 宝石、武器如何修改以及软件的安装与配置&#xff0c;请看我gg分栏的前两章 第一点&#xff1a;先讲一下自己武器上宝石等级的问题 宝石的代码&#xff1a; 0级升星宝石的代码1480e010 0级火力宝石的代码1…

MySQL 窗口函数

聚合函数作为窗口函数 设聚合函数为op语法结构&#xff1a; op(字段名A) over(partition by 字段名B order by 字段名C rows between D1 and D2) 其中&#xff1a; partition by&#xff1a;按照某一字段将数据进行分组 order by&#xff1a;按照某一字段将数据进行排序&…

SIP协议之呼叫保持(HOLD)

呼叫保持(HOLD)是SIP协议应用中的一个重要功能&#xff0c;用于实现不挂断电话而达到暂停媒体&#xff08;常见于音频&#xff0c;视频很少用&#xff09;的目的&#xff0c;而解保持操作会恢复通话。 一、保持/解保持实现机制 1.1 保持 保持发起方&#xff08;保持方&#x…