设计模式 - 建造者模式 Builder Pattern
总结
什么情况下,适用于builder模式(建造者模式)?
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
为了避免构造函数的参数列表过长、不同的构造函数过多,我们一般有builder模式+setter方法两种解决方案。为何builder更胜一筹?
setter()模式 先通过无参构造函数创建对象,然后再通过 setXXX() 方法来逐一设置需要的设置的成员变量。
这里有个先天的弊端就是:如果各个参数之间,有依赖关系,没有地方去进行这种依赖性检查。很可能到最后,生成出来的对象是不合法的。
而builder模式,在最终调用builder()时,可以在那里进行最后的校验。参考Google Guava 提供的现成的缓存工具类 com.google.common.cache.*
public <K1 extends K, V1 extends V> Cache<K1, V1> build() { this.checkWeightWithWeigher(); this.checkNonLoadingCache(); return new LocalManualCache(this); } private void checkNonLoadingCache() { Preconditions.checkState(this.refreshNanos == -1L, "refreshAfterWrite requires a LoadingCache"); } private void checkWeightWithWeigher() { if (this.weigher == null) { Preconditions.checkState(this.maximumWeight == -1L, "maximumWeight requires weigher"); } else if (this.strictParsing) { Preconditions.checkState(this.maximumWeight != -1L, "weigher requires maximumWeight"); } else if (this.maximumWeight == -1L) { logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight"); } }
1.使用场景
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
2.最佳实现:建造者模式
例如我们现在有如下一个类计算机类Computer
,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer { private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 }
2.1 实现方式
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个
public
的构造函数,参数为Computer中必填的那些参数,cpu 和ram。 - 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个
build()
方法,在其中构建Computer的实例并返回
下面代码就是最终的样子
public class Computer { private final String cpu;//必须 private final String ram;//必须 private final int usbCount;//可选 private final String keyboard;//可选 private final String display;//可选 private Computer(Builder builder){ this.cpu=builder.cpu; this.ram=builder.ram; this.usbCount=builder.usbCount; this.keyboard=builder.keyboard; this.display=builder.display; } public static class Builder{ private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 public Builder(String cup,String ram){ this.cpu=cup; this.ram=ram; } public Builder setUsbCount(int usbCount) { this.usbCount = usbCount; return this; } public Builder setKeyboard(String keyboard) { this.keyboard = keyboard; return this; } public Builder setDisplay(String display) { this.display = display; return this; } public Computer build(){ return new Computer(this); } } //省略getter方法 }
2.2 使用方法
在客户端使用链式调用,一步一步的把对象构建出来。
Computer computer=new Computer.Builder("因特尔","三星") .setDisplay("三星24寸") .setKeyboard("罗技") .setUsbCount(2) .build();
3.传统实现
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。
3.1 折叠构造函数模式(telescoping constructor pattern )
public class Computer { ... public Computer(String cpu, String ram) { this(cpu, ram, 0); } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "罗技键盘"); } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "三星显示器"); } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } }
3.2 setter函数(Javabean 模式)
如下所示
public class Computer { ... public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } public String getRam() { return ram; } public void setRam(String ram) { this.ram = ram; } public int getUsbCount() { return usbCount; } ... }
3.3 这两种方式有什么弊端?
第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。
第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。
为了解决这两个痛点,builder模式就横空出世了。
4. 参考文献
https://zhuanlan.zhihu.com/p/58093669
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-03-08 多线程 - synchronized的对象锁和类锁
2020-03-08 多线程 - 守护线程Daemon是什么
2020-03-08 多线程 - 线程有哪些状态?yield() join()
2019-03-08 SQL Server - 四种排序, ROW_NUMBER() /RANK() /DENSE_RANK() /ntile() over()