引言
在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方法进行设置。
publicclassStudent {
private Stringname;
private intid;
private intage;
private intheight;
publicStudent(){
}
publicvoidsetName(Stringname) {
this.name=name;
}
publicvoidsetId(intid) {
this.id=id;
}
publicvoidsetAge(intage) {
this.age=age;
}
publicvoidsetHeight(intheight) {
this.height=height;
}
publicStringgetName() {
returnname;
}
publicintgetId() {
returnid;
}
publicintgetAge() {
returnage;
}
publicintgetHeight() {
returnheight;
}
@Override
publicStringtoString() {
return"Student{"+
"name='"+name+'\''+
", id="+id+
", age="+age+
", height="+height+
'}';
}
}
这个方法也有比较明显的缺点,首先参数多的时候就需要调用很长的一系列setter方法,另外无法区分哪些是必须设置的成员变量,哪些是可选的成员变量,并且成员变量多的时候很容易漏掉一些setter,并且难以检查出来,很容易造成错误。
三、Builder模式
使用Builder模式使得这一切变得更加简洁,方便使用,先来看看Builder模式下如何编码
packagetech.liujintao.leetcode;
publicclassStudent {
privatefinalStringname;
privatefinalintid;
privatefinalintage;
privatefinalintheight;
privateStudent(StudentBuilderstudentBuilder) {
this.name=studentBuilder.name;
this.id=studentBuilder.id;
this.age=studentBuilder.age;
this.height=studentBuilder.height;
}
publicstaticclassStudentBuilder
{
privateStringname;
privateintid;
privateintage;
privateintheight;
publicStudentBuilder(Stringname,intid)
{
this.name=name;
this.id=id;
}
publicStudentBuilderage(intage)
{
this.age=age;
returnthis;
}
publicStudentBuilderheight(intheight)
{
this.height=height;
returnthis;
}
publicStudentbuild()
{
returnnewStudent(this);
}
}
publicStringgetName() {
returnname;
}
publicintgetId() {
returnid;
}
publicintgetAge() {
returnage;
}
publicintgetHeight() {
returnheight;
}
@Override
publicStringtoString() {
return"Student{"+
"name='"+name+'\''+
", id="+id+
", age="+age+
", height="+height+
'}';
}
publicstaticvoidmain(String[] args) {
Studentstudent=newStudentBuilder("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模式被大家所喜爱,并且在很多著名的开源项目中被采用的原因,特别是在一些需要配置环境参数并且参数众多的场景下。