首先要明白建造者模式是一种创建对象的设计模式。
适用于一个类中有很多属性参数,且当其他地方构建这个类对象的时候又不是所有属性都要用到的时候(这个后面会解释)
来不及解释了,直接上代码。
public class Student {
//姓名
private String mName;
//年龄
private int mAge;
//专业
private String mSubject;
//学历
private String mEducation;
//省份
private String mProvince;
//是否实习过
private Boolean mInternShip;
//是否有女朋友
private Boolean mHaveGirlFriend;
//擅长的语言
private String mLanguage;
}
问题:
现在一个学生要去应聘,所以有一个Student类,里面有八个参数。现在的问题是要创建这个类的实例对象,但是提前是这些属性并不是完全都需要的。比如有些公司只需要提供学生姓名,学校,学历,专业就可以。但是有些公司就会问的很全,甚至可能需要你提供实习信息以及是否有女朋友的情况。所以遇到这种情况该怎么办呢?
第一个解决办法:
1、解决方案
就是通过构建不同的构造函数,比如只要求姓名和年龄就只调用只传姓名和年龄的构造函数。
然后以此类推,要是还需要别的信息,就调用符合条件的构造函数。我下面举几个
//只要求传入名字,年龄
public Student(String name, int age) {
mName = name;
mAge = age;
}
//只要求传入名字,年龄和学科
public Student(String name, int age, String subject) {
mName = name;
mAge = age;
mSubject = subject;
}
//只要求传入擅长语言,学科和是否有女朋友
public Student(String language, String subject, boolean haveGirlFriend) {
mLanguage = language;
mSubject = subject;
mHaveGirlFriend = haveGirlFriend;
}
2、缺点
这里只是列举了几种情况,显而易见,要是想罗列出全部情况,构造函数要写超级多(高中的排列组合我忘记了,谁有兴趣可以算一下写全需要多少构造函数)。而且这样子也不能保证参数的顺序,就是当好几个参数都是一个类型的时候,你很那判断哪个参数对应的是哪个。所以这种情况不可行。
第二个解决办法:
1、解决方法
既然构造函数要写很多是不行的,自然而然会想到set方法,毕竟set方法在这里也就写八个,也是可以接受的。这时候就写一个空的构造函数,然后通过set方法给需要的属性赋值就行。这样就可以通过调用不同属性的set方法从而创建不同的实例对象。代码如下:
Student(){}
public void setName(String mName) {
this.mName = mName;
}
public void setAge(int mAge) {
this.mAge = mAge;
}
public void setSubject(String mSubject) {
this.mSubject = mSubject;
}
public void setEducation(String mEducation) {
this.mEducation = mEducation;
}
public void setProvince(String mProvince) {
this.mProvince = mProvince;
}
public void setInternShip(Boolean mInternShip) {
this.mInternShip = mInternShip;
}
public void setHaveGirlFriend(Boolean mHaveGirlFriend) {
this.mHaveGirlFriend = mHaveGirlFriend;
}
public void setLanguage(String mLanguage) {
this.mLanguage = mLanguage;
}
2、缺点
虽然这种解决方案相比于第一种构造函数的方式是可行的,但是想想两个情况,因为需要手动调用不同的set方法,所以可能会造成调用错误或者漏调的情况。还有一种情况就是对象会产生不一致的状态,当你想要传入5个参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实Student对象并没有创建完成。
第三种解决方法就是使用Builder设计模式,感觉先用文字说明太抽象,所以下面也是直接上代码。
具体代码
1、类中构建
public class Student {
//姓名
private String mName;
//年龄
private int mAge;
//专业
private String mSubject;
//学历
private String mEducation;
//省份
private String mProvince;
//是否实习过
private Boolean mInternShip;
//是否有女朋友
private Boolean mHaveGirlFriend;
//擅长的语言
private String mLanguage;
//只要求传入名字,年龄
//私有的无参构造函数
private Student(){}
public void setName (String name) {
mName = name;
}
public void setAge (int age) {
mAge = age;
}
public void setSubject (String subject) {
mSubject = subject;
}
public void setEducation (String education) {
mEducation = education;
}
public void setProvince (String province) {
mProvince = province;
}
public void setInterShip (boolean interShip) {
mInternShip = interShip;
}
public void setHaveGirlFriend (boolean haveGirlFriend) {
mHaveGirlFriend = haveGirlFriend;
}
public void setLanguage (String language) {
mLanguage = language;
}
//静态内部类Builder中的参数最好与外部类(就是要构建的类)的属性一致,且最好要有默认的初始化。
public static class Builder {
private String mName = "Wang";
private int mAge = 25;
private String mSubject = "Computer";
private String mEducation = "Doctor";
private String mProvince = "Beijing";
private boolean mInternShip = true;
private boolean mHaveGirlFriend = true;
private String mLanguage = "Python";
public Builder setName (String name) {
mName = name;
return this;
}
public Builder setAge (int age) {
mAge = age;
return this;
}
public Builder setSubject (String subject) {
mSubject = subject;
return this;
}
public Builder setEducation (String education) {
mEducation = education;
return this;
}
public Builder setProvince (String province) {
mProvince = province;
return this;
}
public Builder setInterShip (boolean interShip) {
mInternShip = interShip;
return this;
}
public Builder setHaveGirlFriend (boolean haveGirlFriend) {
mHaveGirlFriend = haveGirlFriend;
return this;
}
public Builder setLanguage (String language) {
mLanguage = language;
return this;
}
public Student build() {
Student student = new Student();
student.setName(mName);
student.setAge(mAge);
student.setSubject(mSubject);
student.setEducation(mEducation);
student.setProvince(mProvince);
student.setHaveGirlFriend(mHaveGirlFriend);
student.setInterShip(mInternShip);
student.setLanguage(mLanguage);
return student;
}
}
}
2、测试代码
package firstPackage;
public class TestBuilder {
public static void main(String[] args) {
//利用Builder模式创建一个Student对象实例
Student student = new Student.Builder()
.setName("Li")
.setAge(30)
.setProvince("LiaoNing")
.setLanguage("Java")
.setSubject("communication engineering")
.build();
System.out.println("姓名: " + student.getName());
System.out.println("年龄: " + student.getAge());
System.out.println("学科: " + student.getSubject());
System.out.println("学历: " + student.getEducation());
System.out.println("省份: " + student.getProvince());
System.out.println("是有有实习经历: " + student.getInternShip());
System.out.println("是否有女朋友: " + student.getHaveGirlFriend());
System.out.println("擅长的语言: " + student.getLanguage());
}
}
3、测试结果
看完了代码,现在是解惑时间。
先看测试代码部分,也就是构建Student对象实例的部分
逐步分析
1、new Student.Builder这部分是创建一个Builder对象,这个,所以下面的.调用(一直到.build()之前都可以看作是一个Builder对象在调用Builder中属性的set方法),所以在.build()之前相当于创建了一个Builder对象,并且给这个对象的部分属性进行了赋值(没有被赋值的部分是用了默认值)。然后再看.buid()方法就是相当于调用了Builder类的build方法,那么这个方法中会创建一个Student实例,并将自己的(Builder)属性调用Student的set方法赋值给Student的属性。然后返回这个student对象,从而达到创建Student对象实例的事情。
2、所以这也是为什么要将Builder中的属性定义和Student类一样的原因,同时为什么要将Builder中的属性初始化的原因,因为在构建的时候,可能不是所有属性都赋值的,比如我们例子中就赋值了五个属性,那么其他属性就需要用这个默认初始值来代替。
总结
通过Builder创建对象的过程就是
(1)在要创建对象的类中定义一个静态内部类,一般命名为Builder。
(2)在Builder类中定义和外部类一样的参数,而且进建议都初始化。
(3)在Builder中定义返回类型为Builder的各个属性的set方法。
(4)在Builder中定义一个类型为外部类的方法,一般命名为build,在这其中创建外部类对象,将Builder中的参数赋值给外部类对象的参数,然后返回这个外部类对象。
使用过程
另一种写法
构建者的核心就是先给Builder中的属性赋值,然后再用Builde中属性值给外部类中对应的属性值赋值,所以上诉的(4)可以改一种写法,就是这个赋值的过程不放在build方法中,二是放在外部类的构造函数中。具体写法如下,其实就相当于将赋值过程迁移到了外部方法的构造函数中,此时build方法就直接返回外部类对象就好。
package firstPackage;
public class Student {
//姓名
private String mName;
//年龄
private int mAge;
//专业
private String mSubject;
//学历
private String mEducation;
//省份
private String mProvince;
//是否实习过
private Boolean mInternShip;
//是有有女朋友
private Boolean mHaveGirlFriend;
//擅长的语言
private String mLanguage;
//只要求传入名字,年龄
//私有的无参构造函数
private Student(Builder builder) {
this.mName = builder.mName;
this.mAge = builder.mAge;
this.mSubject = builder.mSubject;
this.mEducation = builder.mEducation;
this.mProvince = builder.mProvince;
this.mInternShip = builder.mInternShip;
this.mHaveGirlFriend = builder.mHaveGirlFriend;
this.mLanguage = builder.mLanguage;
}
public void setName (String name) {
mName = name;
}
public void setAge (int age) {
mAge = age;
}
public void setSubject (String subject) {
mSubject = subject;
}
public void setEducation (String education) {
mEducation = education;
}
public void setProvince (String province) {
mProvince = province;
}
public void setInterShip (boolean interShip) {
mInternShip = interShip;
}
public void setHaveGirlFriend (boolean haveGirlFriend) {
mHaveGirlFriend = haveGirlFriend;
}
public void setLanguage (String language) {
mLanguage = language;
}
public String getName() {
return mName;
}
public int getAge() {
return mAge;
}
public String getSubject() {
return mSubject;
}
public String getEducation() {
return mEducation;
}
public String getProvince() {
return mProvince;
}
public Boolean getInternShip() {
return mInternShip;
}
public Boolean getHaveGirlFriend() {
return mHaveGirlFriend;
}
public String getLanguage() {
return mLanguage;
}
//静态内部类Builder中的参数最好与外部类(就是要构建的类)的属性一致,且最好要有默认的初始化。
public static class Builder {
private String mName = "Wang";
private int mAge = 25;
private String mSubject = "Computer";
private String mEducation = "Doctor";
private String mProvince = "Beijing";
private boolean mInternShip = true;
private boolean mHaveGirlFriend = true;
private String mLanguage = "Python";
public Builder setName (String name) {
mName = name;
return this;
}
public Builder setAge (int age) {
mAge = age;
return this;
}
public Builder setSubject (String subject) {
mSubject = subject;
return this;
}
public Builder setEducation (String education) {
mEducation = education;
return this;
}
public Builder setProvince (String province) {
mProvince = province;
return this;
}
public Builder setInterShip (boolean interShip) {
mInternShip = interShip;
return this;
}
public Builder setHaveGirlFriend (boolean haveGirlFriend) {
mHaveGirlFriend = haveGirlFriend;
return this;
}
public Builder setLanguage (String language) {
mLanguage = language;
return this;
}
public Student build() {
return new Student(this);
}
}
}
这样写在构建的时候语句不用动,最终结果还是一样的。
当得知有的参数是必要的时候
之前我们说构建者模式一般适用于类中参数很多而且参数不确定是否有必要的的情况。下面有一种情况就是某个或者某几个参数是必要的,那么建议这之后将这几个参数方法Builder的构造函数中,因为在构建的时候一定会调用Builder的构造函数,这样将参数传进去就会让必要的参数一定被赋值。比如说学生的名字和年龄是一定要有的。那么Builder类可以改写如下:
public static class Builder {
private String mName = "Wang";
private int mAge = 25;
private String mSubject = "Computer";
private String mEducation = "Doctor";
private String mProvince = "Beijing";
private boolean mInternShip = true;
private boolean mHaveGirlFriend = true;
private String mLanguage = "Python";
public Builder(String name, int age) {
mName = name;
mAge = age;
}
public Builder setName (String name) {
mName = name;
return this;
}
public Builder setAge (int age) {
mAge = age;
return this;
}
public Builder setSubject (String subject) {
mSubject = subject;
return this;
}
public Builder setEducation (String education) {
mEducation = education;
return this;
}
public Builder setProvince (String province) {
mProvince = province;
return this;
}
public Builder setInterShip (boolean interShip) {
mInternShip = interShip;
return this;
}
public Builder setHaveGirlFriend (boolean haveGirlFriend) {
mHaveGirlFriend = haveGirlFriend;
return this;
}
public Builder setLanguage (String language) {
mLanguage = language;
return this;
}
public Student build() {
return new Student(this);
}
}
此时的构建语句如下:
Student student = new Student.Builder("Li",30)
.setProvince("LiaoNing")
.setLanguage("Java")
.setSubject("communication engineering")
.build();
运行结果也是一致的。
构建者模式通过不同的构建属性方式从而可以构建出同一个类的不同对象实例。