构造器参数过多时考虑使用构建器(Builder)
一.静态工厂和构造器的局限性
面对需要大量可选参数才能构建对象时,静态工厂和构造器并不能随着可选参数的增加而合理扩展。
假设创建一个类Person需要使用大量的可选参数,其中两个参数是必填的,剩下的都是可选的,面对这种情况在使用静态工厂和构造器创建对象时通常使用叠加的方式实现:
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private final String name; // 必填
private final int age; // 必填 private final int gender; // 可选
private final String tel; // 可选
private final String address; //可选
private final String school; // 可选 // 使用叠加的方式处理可选参数
public Person(String name, int age) {
this(name, age, 0);
} public Person(String name, int age, int gender) {
this(name, age, gender, null);
} public Person(String name, int age, int gender, String tel) {
this(name, age, gender, tel, null);
} public Person(String name, int age, int gender, String tel, String address) {
this(name, age, gender, tel, address, null);
} public Person(String name, int age, int gender, String tel, String address, String school) {
this.name = name;
this.age = age;
this.gender = gender;
this.tel = tel;
this.address = address;
this.school = school;
}
}
上例中如果可选参数变得越来越多,那么代码维护上将会很困难,并且使用者也容易出错(比如不小心传错了位置顺序),所以这种重叠构造器模式在处理可选参数不多的情况是可行的,但是参数多了后就不合适了。
除了使用叠加的方式外,还可以使用setter处理这种情况(我们一般工作中应该大部分就是这样)即使用无参构造器创建对象,然后调用setter设置必要参数以及可选参数:
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private String name = ""; // 必填
private int age = 18; // 必填 private int gender = 0; // 可选
private String tel = ""; // 可选
private String address = ""; //可选
private String school = ""; // 可选 public void setName(String name) {
this.name = name;
} public void setAge(int age) {
this.age = age;
} public void setGender(int gender) {
this.gender = gender;
} public void setTel(String tel) {
this.tel = tel;
} public void setAddress(String address) {
this.address = address;
} public void setSchool(String school) {
this.school = school;
} public static void main(String[] args) {
Person p = new Person();
p.setName("TT");
p.setAge(18); p.setGender(1);
p.setTel("110");
p.setAddress("中国");
p.setSchool("NJ");
}
}
使用setter比起叠加构造器创建对象要容易,代码可读性更好,但是存在严重缺陷。一个是对象的创建被分解到多次setter中,没法保证最后得到的对象就是想要的,因为在setter过程中参数可能被改变。与此相关的就是此种方式下类无法成为不可变类(使用了setter后,类中的字段没法设置成final),会存在线程安全问题。
二.使用Builder兼具安全性以及可读性
使用Builder构建器具有像重叠构造器那样的安全性(不可变类)同时还像setter具有很好的可读性。
Builder构建器方式是指不直接创建想要的对象,而是调用者先用必填参数调用构造器(或者静态工厂方法)得到builder对象。然后调用者使用builder对象调用类似setter方法来设置相关的可选参数。最后调用者在调用builder中无参的build方法生成不可变对象。Builder是自身所能构建类的静态成员类(静态内部类):
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private final String name; // 必填
private final int age; // 必填 private final int gender; // 可选
private final String tel; // 可选
private final String address; //可选
private final String school; // 可选 public static class Builder {
private final String name; // 必填
private final int age; // 必填 private int gender = 0; // 可选
private String tel = ""; // 可选
private String address = ""; //可选
private String school = ""; // 可选 public Builder(String name, int age) {
this.name = name;
this.age = age;
} public Builder gender(int gender) {
this.gender = gender;
return this;
} public Builder tel(String tel) {
this.tel = tel;
return this;
} public Builder address(String address) {
this.address = address;
return this;
} public Builder school(String school) {
this.school = school;
return this;
} public Person build() {
return new Person(this);
}
} private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.tel = builder.tel;
this.address = builder.address;
this.school = builder.school;
} public static void main(String[] args) { Person person = new Builder("WH", 18)
.gender(1)
.tel("110")
.address("WH")
.school("NJ")
.build(); }
}
上面使用Builder构造器时可以保证Person是不可变类,同时使用链式调用代码可读性更好易于阅读。
可以在build方法或者类似setter的方法中对参数进行校验,使代码更健壮。
Builder模式十分灵活,利用一个builder对象可以创建多个对象,且在创建对象时可以对参数进行修改,比如自动补全某参数或者对某参数做计算后在赋值或者自动生成主键id。
Builder构建器的对象还可以作为参数传递给创建对象的方法,让创建对象的方法利用Builder构建器生成对象,同时利用范型就可以在还没有Builder的情况下编写代码:
// 利用范型定义通用的Builder
public interface Builder<T> {
T build();
} // 利用builder对象创建对象的方法
public Person newInstance(Builder<? extends Person> builder) {
return builder.build();
}
三.利用反射创建对象存在的问题
一帮我们也会使用Class.newInstance()创建对象,首先通过Class.newInstance创建对象默认调用目标对象中的无参构造器(private修饰的无参也算,默认的无参也算),那么当存在多个构造器时newInstance就无效了,且在编译阶段是没法发现这个错误的仅运行时才知道:
public class Parent { // 使默认无参构造器失效
public Parent(int i) {} public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class clz = Parent.class;
Parent p = (Parent) clz.newInstance();
} }
上例在运行时会报错。
四.Builder构建器的缺点
为了创建目标对象需要先创建Builder对象,在十分注重性能的情况下可能对性能产生影响。同时在代码量上比重叠构造器还要冗长。所以尽量在参数较多时使用Builder构建器。如果一开始参数不多可是后面参数可能会增加变得比较多那就最好一开始使用Builder构建器,因为如果开始使用的重叠构造器或者静态工厂方法,等到多参数时改成Builder构建器,那么过时的构造器或静态工厂方法由于被使用不能删除会使代码结构显得不协调。简单的说,如果类的构造器或者静态工厂方法中具有多个参数,特别是大多数参数还是可选的时候,使用Builder构建器是个不错的选择。使用Builder构建器的调用者的代码更易编写和阅读,同时使用构建器使目标类不可变从而更线程安全。
最新文章
- SVN-Server搭建及配置
- 关于c#的一些笔记
- String, StringBuffer, StringBuilder(转载)
- Delphi 中的哈希表(二)—— TStringHash
- JAVA 想让类无法new,可以使用private将类的构造函数改为私有的,这样new的时候就会报错了
- OpenThreadToken,OpenProcessToken DuplicateToken 取得句柄的令牌
- Android 常用UI控件之TabHost(5)Tab栏在底部且在最上层也不盖tab页
- CSU 1515 Sequence (莫队算法)
- centos6.7下 编译安装MySQL5.7
- JAVA定时任务调度之Timer入门详解(一)
- 前端工程化webpack(一)
- H5 35-背景平铺属性
- ES系列一、CentOS7安装ES 6.3.1、集成IK分词器
- 使用@property - 廖雪峰的官方网站
- AJAX删除事件与加载数据
- Spring Cloud常用组件介绍
- 蓝桥杯 ALGO-1:区间k大数查询
- iOS中清除缓存的方法 以及SDWebimage自带的清除缓存方法
- TCP/IP状态详解[转]
- js学习笔记30----对象