1.序
当我们创建实例,遇到大量的参数,有的参数需要传值,有的参数不需要选择等等灵活的条件下,我们应该怎么办?
1.重叠构造器:简单粗暴,提供好多好多个构造函数来满足需要。
2.JavaBeans 模式:实际上是先调用无参的构造函数来创建对象,然后再调用属性的 setter 方法来设置每个必要的参数,以及每个可选参数。
3.Builder 模式:此模式不直接生成想要的对象。首先客户端使用必要的参数调用构造器(或静态工厂方法),得到一个builder对象;然后客户端在 builder 对象上调用类似于 setter 的方法来设置可选参数;最后调用无参的 build 方法来生成不可变的对象,这个builder是它构建的类的静态成员类。
2.静态工厂和构造器模式:将所有参数传递到构造函数中
缺点:不能很好的扩展到大量参数
示例
当前有一个用户类如下:
public class UserConstruct {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份证号
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必须都有的情况下
* false : 上述属性有一个或多个为空
*/
private boolean infoIsComplete;
public UserConstruct(Long id, String name) {
this(id, name, null, null);
}
public UserConstruct(Long id, String name, String idNumber) {
this(id, name, idNumber, null);
}
public UserConstruct(Long id, String name, String idNumber, String address) {
this.id = id;
this.name = name;
this.idNumber = idNumber;
this.address = address;
if (null != id && null != name
&& null != idNumber && null != address) {
this.infoIsComplete = true;
}
}
}
例子中的UserConstruct.java类共有5个成员变量,其中id,name,infoIsComplete是必选成员变量且infoIsComplete不是通过用户设置的,而是根据对象实际的属性设值情况进行赋值的。而idNumber,address则是两个可选变量。 当我们需要创建一个拥有地址而没有身份证号码的对象时,我们会这么写:
UserConstruct user = new UserConstruct(1L, "张三", null, "上海市");
你可能会想这么看起来并没有什么问题啊,但是如果当我们的可选参数不是两个而是50个的时候,构造器的参数会多么的复杂难读,而且稍有不慎就会出现参数顺序错乱的错误进而导致整个程序运行出错。 也就是说,在可选参数的数量多的情况下,虽然重叠构造器可行,但是会有诸如难读易出错的问题出现。 所以不建议使用。
3.javaBean模式:调用一个无参数的构造器,然后调用setter方法设置参数
缺点:必要参数的设置可能会遗漏,参数过多的时候会有很多set过程
public class UserJavaBean {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份证号
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必须都有的情况下
* false : 上述属性有一个或多个为空
*/
private boolean infoIsComplete;
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
public void setAddress(String address) {
this.address = address;
}
public void setInfoIsComplete(boolean infoIsComplete) {
this.infoIsComplete = infoIsComplete;
}
}
那么在JavaBean模式下我们初始化一个重叠构造器中例子就变成了下面这样:
UserJavaBean user = new UserJavaBean();
user.setId(1L);
user.setName("张三");
user.setAddress("上海市");
user.setInfoIsComplete(false);
- 无法保证必要参数 这种方式我们没办法保证所有的必要参数都如我们所愿地被赋上了值,当然了这个缺点还有弥补的方案,就是我们通过调用一个包含所有必要参数的构造器来获取一个对象,而只通过setter方法来设置相关的可选参数。
- 构造过程中JavaBean可能会处于不一致的状态
1.假设infoIsComplete这个字段必须在所有的属性都被赋值后才为true,这样通过JavaBean的方式我们也没有很好的方式来通过代码的方式自动为其赋值。
2.还有一点就是,如果idNumber和address两个两个可选参数必须同时存在的时候,使用JavaBean也是有点力不从心了。 - JavaBean模式阻止了把类变成不可变的可能 一旦我们使用了JavaBean模式,则表明我们会为类的每个属性都编写一个共有的setter方法,也就说明我们的类无法成为一个不可变类
4.Builder模式
客户端利用所有必要的参数调用构造器(或静态工厂方法),得到一个builder对象,然后客户端在builder对象上调用类似setter方法的方式,来设置每个相关可选参数。
public class UserBuilder {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份证号
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必须都有的情况下
* false : 上述属性有一个或多个为空
*/
private boolean infoIsComplete;
/**
* 构建器
*/
public static class Builder {
// 必传参数
private Long id;
private String name;
// 可选参数
private String idNumber;
private String address;
public Builder(Long id, String name) {
this.id = id;
this.name = name;
}
public Builder idNumber(String idNumber) {
this.idNumber = idNumber;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public UserBuilder build() {
return new UserBuilder(this);
}
}
private UserBuilder(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.idNumber = builder.idNumber;
this.address = builder.address;
// 根据参数赋值情况,给infoIsComplete赋值
if (this.id != null && this.name!= null
&& this.idNumber!= null && this.address != null) {
this.infoIsComplete = true;
}
}
}
还是上面的例子,我们就可以改写为:
UserBuilder user = new Builder(1L, "张三").address("上海市").build();
是不是同样的易读,而且我们还可以在UserBuilder的构造器中进行参数的验证,并且可以顺利的给infoIsComplete属性自动赋值。 构建器有下面几个优点:
5.优势
- 可以和构造器一样对参数强加约束条件,比如idNumber和address必须同时存在。
- 可以拥有多个可变参数
- 十分灵活,可以通过单个Builder构建多个对象,也可在构建对象时对参数进行调整。
6.缺点
当然了,构建器也有不足的地方,为了创建一个对象,我们首先必须创建一个它的构建器对象,这可能在一定程度上会消耗我们的内存。所以在一些比较注重性能的情况下构建器就不那么好使了。但是在极大多数的情况下,建议我们在还是在当前或者未来参数数量比较大的类中使用构建器。
7.场景
如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选参数时。
与传统的重叠构造器模式相比使用Builder模式的客户端代码将更容易阅读和编写,构建器也比JavaBeans更加安全。
但是将来你可能需要添加参数,如果一开始就使用构造器或者静态工厂,等到类需要多个参数时才添加构建器,就会无法控制,那些过时的构造器或者静态工厂显得十分不协调。所以建议一开始就使用构建器.
8.Lombok的 @Builder注解使用
Lombok大家都知道,在使用POJO过程中,它给我们带来了很多便利,省下大量写get、set方法、构造器、equal、toString方法的时间。除此之外,通过@Builder注解,lombok还可以方便的时间建造者模式。
@Builder@ToStringpublic class NutritionFacts {
//都设置为final类型,不可变对象,以保证线程的安全 private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static void main(String[] args){
NutritionFacts cocaCola = NutritionFacts.builder().
calories(100).sodium(35).carbohydrate(27).build();
System.out.print(cocaCola);
}}
太复杂的bean类不建议用lombok的注解了,比如继承等,因为可能会有一些小问题,建议大家在用的时候详细看下lombok这个注解的使用方法和坑。
9.参考文献
https://juejin.cn/post/6844903741171171336
https://zhuanlan.zhihu.com/p/95493504
本公众号分享自己从程序员小白到经历春招秋招斩获10几个offer的面试笔试经验,其中包括【Java】、【操作系统】、【计算机网络】、【设计模式】、【数据结构与算法】、【大厂面经】、【数据库】期待你加入!!!
1.计算机网络----三次握手四次挥手
2.梦想成真-----项目自我介绍
3.你们要的设计模式来了
4.一字一句教你面试“个人简介”
5.接近30场面试分享
6.你们要的免费书来了