【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.hqwc.cn/news/56724.html

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

相关文章

SpringBoot使用redis作为缓存的实例

目录 什么是缓存&#xff1f; 缓存的作用&#xff1f; 缓存的成本&#xff1f; 实际项目中的应用 代码展示 什么是缓存&#xff1f; 缓存就是数据交换的缓冲区&#xff08;称作Cache [ kʃ ] &#xff09;&#xff0c;是存贮数据的临时地方&#xff0c;一般读写性能较高。 缓…

睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程

简介&#xff1a; 睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程 支持分享海报 支持暗黑模式 包含了音频数据 最近很火的助眠小程序&#xff0c;前端vue&#xff0c;可以打包H5&#xff0c;APP&#xff0c;小程序 后台可以设置流量主广告&#xff0c;非常不错的源码 代码完…

SpringBoot3 整合Prometheus + Grafana

通过Prometheus Grafana对线上应用进行观测、监控、预警… 健康状况【组件状态、存活状态】Health运行指标【cpu、内存、垃圾回收、吞吐量、响应成功率…】Metrics… 1. SpringBoot Actuator 1. 基本使用 1. 场景引入 <dependency><groupId>org.springframew…

SpringIOC注入的两种方式讲解以及代码示例

Ioc是Spring全家桶各个功能模块的基础&#xff0c;创建对象的容器。 AOP也是以IoC为基础&#xff0c;AOP是面向切面编程&#xff0c;抽象化的面向对象 AOP功能&#xff1a;打印日志&#xff0c;事务&#xff0c;权限处理 AOP的使用会在下一篇文章进行介绍 IoC 翻译为控制反…

排序算法(二)

1.希尔排序-Shell Sort 1.算法原理 将未排序序列按照增量gap的不同分割为若干个子序列&#xff0c;然后分别进行插入排序&#xff0c;得到若干组排好序的序列&#xff1b; 缩小增量gap&#xff0c;并对分割为的子序列进行插入排序&#xff1b;最后一次的gap1&#xff0c;即整个…

CISCO MDS 9148 SAN Switch 交换机命令配置方法:

前言 CISCO MDS 9148 SAN 交换机已经停产&#xff0c;但还是要掌握一下配置的方法&#xff1a; 升级款后面 9148S 或者 9100系列&#xff0c;但配置方式基本都差不多&#xff0c;掌握一个就好&#xff1a; 高性能和极具吸引力的价值 Cisco MDS 9148S 16G 多层光纤交换机是下…

超详情的开源知识库管理系统- mm-wiki的安装和使用

背景&#xff1a;最近公司需要一款可以记录公司内部文档信息&#xff0c;一些只是累计等&#xff0c;通过之前的经验积累&#xff0c;立马想到了 mm-wiki&#xff0c;然后就给公司搭建了一套&#xff0c;分享一下安装和使用说明&#xff1a; 当前市场上众多的优秀的文档系统百…

RISC-V基础之函数调用(二)栈与寄存器(包含实例)

堆栈是一种后进先出&#xff08;LIFO&#xff09;的队列&#xff0c;用于存储函数调用时的临时数据和现场数据。堆栈指针sp&#xff08;寄存器2&#xff09;是一个普通的RISC-V寄存器&#xff0c;按照惯例&#xff0c;指向堆栈的顶部。堆栈从高地址向低地址增长&#xff0c;即当…

文字转语音

键盘获取文字&#xff0c;转化为语音后保存本地 from win32com.client import Dispatch from comtypes.client import CreateObject from comtypes.gen import SpeechLib speakerDispatch(SAPI.SpVoice) speaker.Speak(请输入你想转化的文字) datainput(请输入&#xff1a;)#s…

3.01 用户在确认订单页收货地址操作

用户在确认订单页面&#xff0c;可以针对收货地址做如下操作&#xff1a; 1. 查询用户的所有收货地址列表 2. 新增收货地址 3. 删除收货地址 4. 修改收货地址 5. 设置默认地址步骤1&#xff1a;创建对应用户地址BO public class AddressBO {private String addressId;private…

Unity CanvasGroup组件

文章目录 1. 简介2. 组件属性2.1 Alpha(透明度)2.2 Interactable(是否为可交互)2.3 Blocks Raycasts(是否接受射线监测)2.4 Ignore Parent Groups(忽视上层的画布组带来的影响) 1. 简介 CanvasGroup(画布组) 组件&#xff0c;可集中控制整组 UI 元素(自身和所有子物体)的某些属…

设计模式行为型——迭代器模式

什么是迭代器模式 迭代器模式&#xff08;Iterator Pattern&#xff09;属于行为型模式&#xff0c;其提供一种方法顺序访问一个聚合对象中的各种元素&#xff0c;而又不暴露该对象的内部表示&#xff0c;即不需要知道集合对象的底层表示。编程环境中非常常用的设计模式。 迭代…