引言
在Java编程中,常常需要为一个Bean构建成员变量或者构建参数,常用的方法有使用构造函数、使用JavaBean的set()方法,但是这两个方案或多或少都存在一定的缺点,于是今天的主角builder模式出场了,它解决了这种典型应用场景的问题,采用简洁明了的使用方式,灵活多变的链式调用,使得多个参数的Bean的构建变得十分简洁。
本文从实际例子来看builder模式到底是什么?如何使用?
以下使用一个Student Bean来进行举例说明。
一、传统的构造函数
传统方式都是使用构造函数在构建Bean,例如存在一个学生类Student,包含id,name,age,height
几个成员变量,其中id
和name
是必须的,age
和height
是可选的。如果使用传统的构造函数,一般情况下编码如下
public class Student {
private final String name;
private final int id;
private final int age;
private final int height;
public Student(String name,int id,int age, int height){
this.name = name;
this.id = id;
this.age = age;
this.height = height;
}
public Student(String name,int id) {
this(name,id,0,0);
}
public Student(String name,int id,int age){
this(name,id,age,0);
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public int getHeight() {
return height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", height=" + height +
'}';
}
}
存在的问题主要有两个
- 构造函数众多,显得十分繁琐,如果参数继续增加,则构造函数会更多
- 无法很好地区分参数,例如
public Student(String name,int id,int age)
,可选地参数是age,但是从构造函数上很难看出来,并且由于height与age一样都是int类型,所以不能出现public Student(String name,int id,int age)
,也就是没办法实现只传入一个age或者一个height参数地情况,而是不得不使用public Student(String name,int id,int age, int height)
,即便我只是想设置一个height不想设置age,我也必须为这个age字段设置一个默认值。
二、JavaBean setter方法
为了解决以上问题,又一个经典地方案出现了,这就是JavaBean的setter方法,需要构造一个参数为空的构造函数,所有的成员变量通过setter方法进行设置。
public class Student {
private String name;
private int id;
private int age;
private int height;
public Student(){
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public int getHeight() {
return height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", height=" + height +
'}';
}
}
这个方法也有比较明显的缺点,首先参数多的时候就需要调用很长的一系列setter方法,另外无法区分哪些是必须设置的成员变量,哪些是可选的成员变量,并且成员变量多的时候很容易漏掉一些setter,并且难以检查出来,很容易造成错误。
三、Builder模式
使用Builder模式使得这一切变得更加简洁,方便使用,先来看看Builder模式下如何编码
package tech.liujintao.leetcode;
public class Student {
private final String name;
private final int id;
private final int age;
private final int height;
private Student(StudentBuilder studentBuilder) {
this.name=studentBuilder.name;
this.id =studentBuilder.id;
this.age = studentBuilder.age;
this.height = studentBuilder.height;
}
public static class StudentBuilder
{
private String name;
private int id;
private int age;
private int height;
public StudentBuilder(String name,int id)
{
this.name = name;
this.id = id;
}
public StudentBuilder age(int age)
{
this.age=age;
return this;
}
public StudentBuilder height(int height)
{
this.height = height;
return this;
}
public Student build()
{
return new Student(this);
}
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public int getHeight() {
return height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", height=" + height +
'}';
}
public static void main(String[] args) {
Student student = new StudentBuilder("codingway",111).age(18).height(2).build();
System.out.println(student.toString());
}
}
首先将Student类中的成员变量都声明为private final,并且将构造函数也声明为private,这样子就保证了无法通过Student的构造函数在构建Student Bean,并且成员变量也无法被修改,对外只提供getter方法用于或者成员变量。
随后构造一个StudentBuilder类,StudentBuilder类是一个静态类 ,其构造函数可以设置为Student必须的属性,例如id和name,其他可选的变量放到方法中,例如age和height,每个方法返回StudentBuilder本身。
Student对象只有一个构造函数,其参数就是StudentBuilder,于是所有成员变量地设置由StudentBuilder接管了,而StudentBuilder控制了哪些成员变量必须赋值,哪些是可选的,最后通过build方法构造Student Bean。使用者一目了然,链式调用更加清晰。
这就是Builder模式被大家所喜爱,并且在很多著名的开源项目中被采用的原因,特别是在一些需要配置环境参数并且参数众多的场景下。