【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(在此…

篇五:原型模式:复制对象的秘密

篇五&#xff1a;"原型模式&#xff1a;复制对象的秘密" 设计模式是软件开发中的重要组成部分&#xff0c;原型模式是创建型设计模式中的一种。原型模式旨在通过复制现有对象来创建新的对象&#xff0c;而不是通过调用构造函数来创建。在C中&#xff0c;原型模式广泛…

nginx负载均衡

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

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

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

Linux 文件与目录管理

nvLinux 文件与目录管理 我们知道 Linux 的目录结构为树状结构&#xff0c;最顶级的目录为根目录 /。 其他目录通过挂载可以将它们添加到树中&#xff0c;通过解除挂载可以移除它们。 在开始本教程前我们需要先知道什么是绝对路径与相对路径。 绝对路径&#xff1a; 路径的写…

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

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

log_softmax比softmax更好?

多类别分类的一个trick 探讨一下在多类别分类场景&#xff0c;如翻译、生成、目标检测等场景下&#xff0c;使用log_softmax的效果优于softmax的原因。 假设词典大小为10&#xff0c;一个词的ID为9&#xff08;即词典的最后一个词&#xff09;&#xff0c;使用交叉熵作为损失函…

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

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

九耶|阁瑞钛伦特 神庙逃亡游戏代码

以下是一个简单的神庙逃亡&#xff08;Temple Run&#xff09;游戏的HTML代码示例&#xff1a; <!DOCTYPE html> <html> <head><title>神庙逃亡</title><style>#game-container {position: absolute;width: 800px;height: 600px;backgrou…

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

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

7.Eclipse中改变编码方式及解决部分乱码问题

1、改变整个工作空间的编码方式&#xff1a; 点击Window->Preference->General->workplace&#xff0c;然后选择默认编码方式 2、改变某个项目的编码方式&#xff1a; 右键点击项目名->Properties>Resource&#xff0c;然后选择默认编码方式。 问题&#xff…

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.为什么需要网关…

TCP-三次握手-四次挥手

前言&#xff1a;网络传输层主要是建立端到端的连接&#xff0c;那TCP通信的连接和断开原理是什么呢&#xff1f;也就是TCP的三次握手和四次挥手是什么意思&#xff1f;下面对这两个概念进行详细的理解和学习。 这篇文章写的不错&#xff0c;很详细。 面试官&#xff0c;不要…

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

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

python计算相关系数R

方法一&#xff1a; import numpy as np# 计算相关系数R def r(y_true, y_pred):y_true np.array(y_true)y_pred np.array(y_pred)corr np.corrcoef(y_true, y_pred)[0][1]return corrcorr r(yture, ypred)方法二 import scipy.stats # 计算皮尔逊相关指数&#xff0c;并…

【LangChain学习】基于PDF文档构建问答知识库(一)前期准备

这系列主要介绍如何使用LangChain大模型&#xff0c;结合ChatGPT3.5&#xff0c;基于PDF文档构建专属的问答知识库。 一、 环境搭建 LangChain 和 OpenAI 本身可支持 Nodejs 和 Python 两个版本&#xff0c;笔者后续的介绍主要用到Python版本&#xff0c;如果有需要Nodejs版本…

Spring 创建和使用

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

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

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