读完本文你将了解到:
帅气的 Builder 链式调用
在日常开发中,经常可以看到这样的代码:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
或者
new AlertDialog.Builder(this)
.setTitle("hello")
.setMessage("I'm shixinzhang")
.setIcon(R.drawable.bg_search_corner)
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
//...
}
})
.show();
可以看到这样链式调用看起来好整齐啊,Builder 模式早有耳闻,今天就来详细了解一下。
常见的两种构建方式
在日常开发中,我们经常需要给某个对象的变量赋值,这个赋值的过程称为 对象的构建。
比如现在有个 Person 类,它有几个成员变量:
//固定不变的对象,一般变量需要声明为 final
private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation; //可选
private String mJob; //可选
private String mHabit; //可选
常见的构建方式之一:定义多个重载的构造函数
public class PersonOne {
//固定不变的对象,一般变量需要声明为 final
private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation; //可选
private String mJob; //可选
private String mHabit; //可选
public PersonOne(String name) {
mName = name;
}
public PersonOne(String location, String name) {
mLocation = location;
mName = name;
}
public PersonOne(String name, String location, String job) {
mName = name;
mLocation = location;
mJob = job;
}
public PersonOne(String name, String location, String job, String habit) {
mName = name;
mLocation = location;
mJob = job;
mHabit = habit;
}
}
这种方式的优点:简单。
看起来很简单嘛,每个构造函数都需要啥参数,第几个参数是干什么的,看一下就知道了,嗯,很直观!
等等!这是你站在开发者的角度的结果!
使用者使用时可得仔细了解你每个构造函数,否则一不小心填错顺序也不知道。
而且如果有十几个属性,我靠,你见过有十几个参数的构造函数吗?
所以缺点是:
只适用于成员变量少的情况,太多了不容易理解、维护。
常见的构建方式之二:使用 setter 方法挨个构造
吸取上面的教训,我不在构造方法里穿参数了,改成用 set 方法挨个构造,可以了吧。
public class PersonTwo {
//固定不变的对象,一般变量需要声明为 final
private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation; //可选
private String mJob; //可选
private String mHabit; //可选
public PersonTwo(String s) {
this.mName = s;
}
public String getName() {
return mName;
}
public String getLocation() {
return mLocation;
}
public void setLocation(String location) {
mLocation = location;
}
public String getJob() {
return mJob;
}
public void setJob(String job) {
mJob = job;
}
public String getHabit() {
return mHabit;
}
public void setHabit(String habit) {
mHabit = habit;
}
}
这种方式也是常见的构造方式,它的好处是:易于阅读,并且可以只对有用的成员变量赋值;
缺点是:
- 成员变量不可以是 final 类型,失去了不可变对象的很多好处;
- 对象状态不连续,你必须调用 4 次 setter 方法才能得到一个具备 4 个属性值得变量,在这期间用户可能拿到不完整状态的对象。
而且使用起来也不好看:
PersonTwo personTwo = new PersonTwo("shixin");
personTwo.setJob("洗剪吹");
personTwo.setLocation("温州");
personTwo.setHabit("嘿嘿嘿");
如果有 N 个属性岂不是要 personTwo.setXXX N 回?不优雅!
即使把 setXXX 方法返回值改成当前构造类,但还是不满足最重要的缺点的第二点:
用户可能拿到不完整状态的对象。
什么意思呢?
这种方式是 先创建对象、后赋值,用户不知道什么时候拿到的对象是完整的,构建完成的。很有可能你只 set 了一两个属性就返回了,一些必要的属性没有被赋值。
优雅的构建方式:变种 Builder 模式
为了解决上述两种构建方式,伟大的程序员们创造出了 变种 Builder 模式。
先来看看用 变种 Builder 模式怎么实现上述 Person 对象的构建吧:
public class PersonThree {
//固定不变的对象,一般变量需要声明为 final
private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation; //可选
private String mJob; //可选
private String mHabit; //可选
/**
* 构造方法的参数是它的 静态内部类,使用静态内部类的变量一一赋值
* @param builder
*/
public PersonThree(Builder builder) {
this.mName = builder.mName;
this.mLocation = builder.mLocation;
this.mJob = builder.mJob;
this.mHabit = builder.mHabit;
}
/**
* PersonTree 的静态内部类,成员变量和 PersonTree 的一致
*/
public static class Builder{
private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation; //可选
private String mJob; //可选
private String mHabit; //可选
/**
* 含必选参数的构造方法
* @param name
*/
public Builder(String name) {
mName = name;
}
public Builder setLocation(String location) {
mLocation = location;
return this;
}
public Builder setJob(String job) {
mJob = job;
return this;
}
public Builder setHabit(String habit) {
mHabit = habit;
return this;
}
/**
* 最终构建方法,返回一个 PersonTree 对象,参数是当前 Builder 对象
* @return
*/
public PersonThree build(){
return new PersonThree(this);
}
}
}
可以看到,变种 Builder 模式包括以下内容:
- 在要构建的类内部创建一个静态内部类 Builder
- 静态内部类的参数与构建类一致
- 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
- 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象
- 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象
调用代码:
new PersonThree.Builder("shixinzhang")
.setLocation("Shanghai")
.setJob("Android Develop")
.setHabit("LOL")
.build();
变种 Builder 模式目的在于减少对象创建过程中引入的多个构造函数、可选参数以及多个 setter 过度使用导致的不必要的复杂性。
好处就是文章开头所说的:
- 看起来很整齐;
- 先赋值,后创建对象。
最终调用 build() 方法才创建了构建类的对象,保证了状态的完整性。
缺点嘛,就是需要额外写的代码多了点。
不过还好,有前辈开发出了 Android Studio 插件来拯救我们。
Android Studio 中使用插件自动生成 变种 Builder 模式代码
第一步:下载插件 Inner Builder:
第二步:重启 Andriod Studio;
第三步:写好要构建的类的变量:
比如:
public class PersonTest {
private final String mName;
private int mAge;
private String mLocation;
}
第四步:按 Control + Insert (Mac :command + N):
第五步:在弹出的 Generate 对话框中选择 Builder:
第六步:选中要使用 Builder 构建的对象,然后勾选使用的配置,点击 OK :
public class PersonTest {
private final String mName;
private int mAge;
private String mLocation;
private PersonTest(Builder builder) {
mName = builder.mName;
mAge = builder.mAge;
mLocation = builder.mLocation;
}
public static final class Builder {
private String mName;
private int mAge;
private String mLocation;
public Builder() {
}
public Builder mName(String mName) {
this.mName = mName;
return this;
}
public Builder mAge(int mAge) {
this.mAge = mAge;
return this;
}
public Builder mLocation(String mLocation) {
this.mLocation = mLocation;
return this;
}
public PersonTest build() {
return new PersonTest(this);
}
}
}
基本上和我们手写的一致,是不是方便很多呢?
总结
经典的 Builder 模式定义为:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
它的重点在于:抽象出对象创建的具体步骤到一个接口,通过调用不同的接口实现,从而得到不同的结果。
Builder 模式在 Android 开发中演变出了 变种 Builder 模式,它除了具备经典构建者模式的功能,还简化了构建的过程,使得创建过程更加简单、直观。
Thanks
《Android 高级进阶》这本书的目录值得一看