Builder模式详解及其在Android开发中的应用
一、引言
在Android开发中,采用Builder模式的代码随处可见,比如说Android系统对话框AlertDialog的使用或者是Android中的通知栏(Notification)的使用,又比如说在一些常用的第三方库中也随处可见其踪迹,比如说一些常用的网络请求库如OkHttp或者是retrofit,又或者是图片加载库Glide中也不缺乏它的应用。
为什么Builder模式在Android或是Java开发中这么火呢?因为它相较于构造函数或者是Get/Set方法,它的灵活性和封装性上都比较有优势。下面就通过具体的例子说明下Builder模式的具体使用方式,直接结合代码会更加直观。
二、类实例初始化需求的实现
首先提出我们的需求:
现在我们想要实例化一个类,如下所示,其中有些属性是必选的,而有些属性是可选的。如下所示:
public class User { private final String mFirstName; //必选 private final String mLastName; //必选 private final String mGender; //可选 private final int mAge; //可选 private final String mPhoneNo; //可选 }
方法一:
** * 定义多个重载的构造函数实现 * Created by DB on 2017/6/23. */ public class User3 { private final String mFirstName; private final String mLastName; private final String mGender; private final int mAge; private final String mPhoneNo; public User3(String mFirstName,String mLastName){ this(mFirstName,mLastName,""); } public User3(String mFirstName,String mLastName,String mGender){ this(mFirstName,mLastName,mGender,0); } public User3(String mFirstName,String mLastName,String mGender,int mAge){ this(mFirstName,mLastName,mGender,mAge,""); } public User3(String mFirstName, String mLastName, String mGender,int mAge,String mPhoneNo) { this.mFirstName = mFirstName; this.mLastName = mLastName; this.mGender = mGender; this.mAge = mAge; this.mPhoneNo = mPhoneNo; } }
在方法一中,我们使用了多个重载的构造函数来实现初始化不同属性值的需求,而可想而知,如果想要实现所有可能性的属性需求,会产生大量重载版本的构造函数,使得代码冗长且不好用。
方法二:
/** * 采用get/set * Created by DB on 2017/6/23. */ public class User4 { private String mFirstName; private String mLastName; private String mGender; private int mAge; private String mPhoneNo; public String getmFirstName() { return mFirstName; } public void setmFirstName(String mFirstName) { this.mFirstName = mFirstName; } public String getmLastName() { return mLastName; } public void setmLastName(String mLastName) { this.mLastName = mLastName; } public String getmGender() { return mGender; } public void setmGender(String mGender) { this.mGender = mGender; } public int getmAge() { return mAge; } public void setmAge(int mAge) { this.mAge = mAge; } public String getmPhoneNo() { return mPhoneNo; } public void setmPhoneNo(String mPhoneNo) { this.mPhoneNo = mPhoneNo; } }
在方法2中,我们使用了get和set方法来完成对属性值的设定和读取,如果是这样的话,就需要将属性值的final关键字去掉,这也就意味着这个类的实例不再是不可变的,与原先的需求有所出入,并且这个类的实例状态不联系,比如说如果你想创建一个同时具有几个属性值的类实例时,只有当最后那个实例属性的set函数被调用时,该实例才具有完整联系的状态,这意味着调用者可能会看到该类实例的不连续状态。
方法3:
终于轮到了我们的Builder模式大展身手了,这里我们使用的Builder模式中的变种模式,具体代码如下:
/** * Created by DB on 2017/6/23. */ public class User { private final String mFirstName; //必选 private final String mLastName; //必选 private final String mGender; //可选 private final int mAge; //可选 private final String mPhoneNo; //可选 //构造函数为私有的,调用者不能直接实例化该类,需要通过builder去实现实例化 private User(UserBuilder builder){ mFirstName=builder.firstName; mLastName=builder.lastName; mGender=builder.gender; mAge=builder.age; mPhoneNo=builder.phoneNo; } //仅提供get方法,而不提供set方法,确保User类的不可变性 public String getmFirstName() { return mFirstName; } public String getmLastName() { return mLastName; } public String getmGender() { return mGender; } public int getmAge() { return mAge; } public String getmPhoneNo() { return mPhoneNo; } //构造函数只接受必选的属性值作为参数,并且只有必选的属性值设置为final,以确保其在构造函数中设置 public static class UserBuilder { private final String firstName; private final String lastName; private String gender; private int age; private String phoneNo; public UserBuilder(String firstName,String lastName){ this.firstName=firstName; this.lastName=lastName; } public UserBuilder gender(String gender){ this.gender=gender; return this; } public UserBuilder age(int age){ this.age=age; return this; } public UserBuilder phoneNo(String phoneNo){ this.phoneNo=phoneNo; return this; } public User build(){ return new User(this); } } public static void main(String[] args){ User user = new UserBuilder("ZHB","Pignet") .gender("male") .age(25) .phoneNo("1234456") .build(); System.out.println(user.getmFirstName()+" "+user.getmLastName()); System.out.println(user.getmAge()+" "+user.getmGender()); } }
在这里我们加入了一个测试用例,其结果如下所示:
从上述代码可以看到我们熟悉的链式编程,这边我们将必选的属性值作为参数传到Builder的构造函数中,并且为final,其他的属性值则通过各自的方法实现设置,最后在build方法中新建一个类的实例对象,并返回。
三、Builder模式的优缺点
优点:将类实例的初始化实现分为构建和表示两部分,同时,也将类实例的初始化从目标类中隔离出来(集中到了一个内部类中),避免了过度的Set方法,同时采用调用链式的实现,使得代码更加简洁明了。
缺点:需要编写很多的样板代码,我们需要在内部类UserBuilder中重复外部类User的属性定义
四、使用插件自动生成代码
为了规避掉Builder模式中的缺点,我们可以在Android Studio中(Intelligent IDEA)通过安装名为InnerBuilder的插件来简化Builder模式的创建过程。
来看看它的生成效果
/** * Created by DB on 2017/6/23. */ public class User2 { private final String mFirstName; private final String mLastName; private final String mGender; private final int mAge; private final String mPhoneNo; private User2(Builder builder) { mFirstName = builder.mFirstName; mLastName = builder.mLastName; mGender = builder.mGender; mAge = builder.mAge; mPhoneNo = builder.mPhoneNo; } public static final class Builder { private String mFirstName; private String mLastName; private String mGender; private int mAge; private String mPhoneNo; public Builder() { } public Builder mFirstName(String val) { mFirstName = val; return this; } public Builder mLastName(String val) { mLastName = val; return this; } public Builder mGender(String val) { mGender = val; return this; } public Builder mAge(int val) { mAge = val; return this; } public Builder mPhoneNo(String val) { mPhoneNo = val; return this; } public User2 build() { return new User2(this); } } }
对比前面的例子,我们不难发现这一段自动生成的代码和我们前面写的代码有一点点不同,我们可以在此基础上根据实际的需求进行修改(如前面的必选项采用构造函数来初始化部分)