1.builder模式(建造者模式)
建造者模式是一种创建型设计模式。它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
使用场景:
①相同方法,不同的执行顺序,产生不同的事件结果时;
②多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;
③产品类非常复杂,或者产品类中的调用顺序不同产生了不同效能,这个时候使用建造者模式非常合适。
优点:使用灵活,易于扩展;不用关心内部实现细节,只注重结果。
缺点:产生多余的Builder对象,消耗内存。
2.builder模式用法
假如有一个类Student,它有很多的属性,但是仅仅姓名和学号是必须赋值的,其他的属性都是可选项:
public class Student {
private final int stuId;//必须
private final String name;//必须
private final int age;//可选
private final int gender;//可选
private final int address;//可选
…//还有很多可选属性
}
那怎么来创建一个Student对象呢?由于每个属性都用final修饰了,说明每个属性都要在构造方法中被初始化,必须提供各种参数数量的构造方法,如果不使用建造者模式,如下:
public class Student {
private final int stuId;//必须
private final String name;//必须
private final int age;//可选
private final int gender;//可选
private final String address;//可选
public Student(int stuId,String name){
this(stuId,name,0,1,"");
}
public Student(int stuId,String name,int age){
this(stuId,name,age,1,"");
}
public Student(int stuId,String name,int age,int gender){
this(stuId,name,age,gender,"");
}
public Student(int stuId,String name,int age,int gender,String address){
this.stuId = stuId;
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}
}
这样做确实可以满足需求,但这还是可选参数不多的情况,如果有很多可选参数,就必须要写很多个构造函数,这将导致代码的可读性和维护性变差,更重要的是当用到这个类的时候会感觉无从下手,到底应该用哪个构造方法呢?应该用两个参数的构造方法还是用三个参数的呢?如果用两个参数的构造方法,那么可选参数的默认值是多少?
更棘手的是,如果只想给Student对象设置address属性而不设置age和gender属性的话怎么办?显然还得再继续添加构造方法,或者只能调用全参的构造方法,然后给age和gender属性设置个默认值。
还有一点,stuId,age,gender都是int类型的,那在创建Student对象时,哪一个int类型的对象代表stuId,哪一个代表age,这还进一步增加了使用成本。
为了解决这些问题,可以修改代码,只设置一个默认的无参构造方法,然后给每个属性添加getter和setter方法:
public class Student {
private int stuId;//必须
private String name;//必须
private int age;//可选
private int gender;//可选
private String address;//可选
public Student(){
}
public int getStuId() {
return stuId;
}
public void setStuId(int stuId) {
this.stuId = stuId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
这种方法看上去可读性和维护性比较好,当使用这个类的时候只需要创建一个空的对象,并且设置我们需要的属性就可以了。
比如这样:
Student stu = new Student();
stu.setStuId(1);
stu.setName(“小明”);
stu.setAge(12);
这样做有两个问题,第一个问题是stu对象没有一个创建完毕的标识,上面的stu对象设置了三个属性,但当别人看到这段代码时,他不确定这个stu对象是只需要这三个属性还是当时作者忘了写完整,除非所有的属性都给set上,别人才能确保你这个对象创建完毕;另一个问题是任何人都可以在我们创建好的基础上继续改变它,也就是继续给它set新的属性或者删除某个已经set的属性,这就会使stu对象具有可变性,引起潜在的风险。
为了解决以上两种方法的所有问题,就需要用到builder设计模式了。
builder模式使用方法如下:
public class Student {
private final int stuId;//必须
private final String name;//必须
private final int age;//可选
private final int gender;//可选
private final String address;//可选
private Student(StudentBuilder builder){
this.stuId = builder.stuId;
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.address = builder.address;
}
public int getStuId() {
return stuId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getGender() {
return gender;
}
public String getAddress() {
return address;
}
public static class StudentBuilder{
private final int stuId;
private final String name;
private int age;
private int gender;
private String address;
public StudentBuilder(int stuId,String name){
this.stuId = stuId;
this.name = name;
}
public StudentBuilder setAge(int age){
this.age = age;
return this;
}
public StudentBuilder setGender(int gender){
this.gender = gender;
return this;
}
public StudentBuilder setAddress(String address){
this.address = address;
return this;
}
public Student build(){
return new Student(this);
}
}
}
注意:
①Student的构造方法是私有的,也就是说不能直接new出Student对象;
②将Student的属性用final修饰了,并且在构造方法中都为它们进行了初始化操作,只提供了getter方法;
③使用builder模式构造出来的对象有更好的可读性;
④StudentBuilder的属性中只给必须的属性添加了final修饰,所以必须在StudentBuilder的构造方法中初始化它们。
使用builder设计模式完美的解决了方法一和方法二的不足,并且兼具他们的优点:具有必填属性和可选属性的区分,更重要的是:可读性很强。唯一的不足是要在StudentBuilder中重复的写一遍Student中的属性。
接下来创建一个Student对象:
public Student getStudent(){
return new Student.StudentBuilder(1,“小明”) //必填属性在构造方法中赋值
.setAge(1) //设置可选属性
.setGender(1) //设置可选属性
.build(); //对象构建完毕,返回Student对象
}
代码非常优雅,这是一个链式调用,用1行代码就搞定,更重要的是可读性非常强,而且通过build()可以很明确的告诉别人Student已经创建完毕。
builder设计模式非常灵活,一个builder可以创建出各种各样的对象,只需要在build()之前调用set方法来为对象赋值。
builder模式另一个重要特性是:它可以对参数进行合法性验证,如果传入的参数无效,可以抛出一个IllegalStateException异常,但是在哪里进行参数合法性验证也是有讲究的:那就是在对象创建之后进行合法性验证。修改StudentBuilder的build()方法:
public Student build(){
Student student = new Student(this);
if (student.getAge()>120){
throw new IllegalStateException(“年龄超出限制”);
}
return student;
}
为什么要先创建对象,再进行参数验证?因为StudentBuilder是线程不安全的,如果先进行参数验证后创建对象,那么创建对象的时候对象的属性可能已经被其他线程改变了。例如下面的代码就是错误的:
public Student build(){
if (age>120){
throw new IllegalStateException(“年龄超出限制”);
}
return new Student(this);
}
最后总结一下builder设计模式:当类中有很多属性的时候,尤其是有很多可选属性的时候,就可以使用builder设计模式,因为这样不仅可以使类使用起来很优雅,而且还可以给类的对象一个创建完成的标识,即build()方法。
3.Android源码中的Builder模式
Android中Builder模式很常见,比如Dialog。
AlertDialog.Builder dialog= new AlertDialog.Builder(this);
dialog.setTitle(“title”)
.setMessage(“message”)
.create()
.show();
Dialog源码分析:
public class AlertDialog extends Dialog implements DialogInterface {
private AlertController mAlert; //Controller, 接受Builder成员变量P中的各个参数
protected AlertDialog(Context context, int theme) {
this(context, theme, true);
}
AlertDialog(Context context, int theme, boolean createContextWrapper) {
super(context, resolveDialogTheme(context, theme), createContextWrapper);
mWindow.alwaysReadCloseOnTouchAtt r();
mAlert = new AlertController( getContext(), this, getWindow());
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title); //实际上调用的是mAlert的setTitle方法
}
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView); //实际上调用的是mAlert的setCustomTitle方法
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
……
// Builder为AlertDialog的内部类
public static class Builder {
private final AlertController.AlertParams P; //存储AlertDialog的各个参数
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, int theme) {
P = new AlertController.AlertParams( new ContextThemeWrapper(context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
…
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setIcon(int iconId) {
P.mIconId = iconId;
return this;
}
public Builder setPositiveButton( CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewSpacingSpecified = false;
return this;
}
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); //构造AlertDialog对象, 并将参数传给AlertDialog
P.apply(dialog.mAlert); //将P中的参数应用在dialog中的mAlert对象中
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside( true);
}
dialog.setOnCancelListener( P.mOnCancelListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener( P.mOnKeyListener);
}
return dialog;
}
}
}
通过Builder来设置AlertDialog中的title,、message、button等参数, 这些参数都存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams中包含了与之对应的成员变量。在调用Builder类的create函数时才创建AlertDialog, 并且将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码段。
看看apply函数的实现 :
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle( mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId >= 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId > 0) {
dialog.setIcon( dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton( DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton( DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton( DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,mNeutralButtonListener, null);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
}
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
} else {
dialog.setView(mView);
}
}
}
就是把P中的参数依次设置到AlertController中, 也就是AlertDialog中的mAlert对象。从AlertDialog的各个setter方法中可以看到,实际上也都是调用了mAlert对应的setter方法。
4.总结
与构造器相比,builder的优势在于,builder可以有多个可变参数,构造器就像方法一样,只能有一个可变参数。因为builder利用单独
的方法来设置每个参数,想要多少个可变参数,它们就可以有多少个,直到每个setter方法都有一个可变参数。
Builder模式十分灵活,可以利用单个builder构建多个对象,builder的参数可以在创建对象期间进行调整,也可以随着不同的对象而改变。builder
可以自动填充其些域,例如每次创建对象时自动增加。
总之,如果类的构建器或者静态工厂有多个参数,Builder模式是不错的选择,特别是当大多数参数都是可选的时候。