Builder设计模式在代码设计中很常见,譬如我们在阅读大神们的源码是,一些初始化参数比较多构造方法都采用了Builder模式,譬如说Okhttp
,Glide
,Picasso
等
Builder模式长什么样?
举个简单的例子,现在大多数Android应用的开发网络层大多数都在使用OkHttp,熟悉的人都知道,在使用Okhttp之前要进行一些初始化工作,譬如说超时时间
,缓存策略
等等,下面是个简单的初始化例子:
public static OkHttpClient get() {
if (okHttpClient == null) {
synchronized (AppHttpClient.class) {
if (okHttpClient == null) {
okHttpClient = new OkHttpClient.Builder()
//cookie保存
.cookieJar(new CookiesManager(new PersistentCookieStore(BaseApplication.getInstance())))
.sslSocketFactory(createSSLSocketFactory())
//支持SSL
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
)
//链接15S超时
.connectTimeout(15, TimeUnit.SECONDS)
//Read请求30S超时
.readTimeout(30, TimeUnit.SECONDS)
//Write请求30S超时
.writeTimeout(30, TimeUnit.SECONDS)
//增加Header
.addInterceptor(new HeaderInfoInterceptor())
//build
.build();
}
}
}
return okHttpClient;
}
上面的链式调用方法进行初始化使用的就是Builder设计模式,是不是觉得很方便?
使用场景
Builder可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。这句话看起来很拗口,不好理解,下边我们举一个简单的例子:
我们在实际开发过程当中,时常会遇到这样一个情况,需要构建一个复杂(属性N多)的对象,像这样婶儿的:
public class Person {
private String name; //必须
private int age; //必须
private int sex; //必须
private float height;
private String phone;
private String address;
private String email;
}
想要new一个这样的类的实例,于是我们就开始撸下边的代码,通过构造函数的参数的方式去new一个对象:
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age, float height, int sex) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
}
public Person(String name, int age, float height, int sex, String phone) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
this.phone = phone;
}
public Person(String name, int age, float height, int sex, String phone, String address) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
this.phone = phone;
this.address = address;
}
public Person(String name, int age, float height, int sex, String phone, String address, String email) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
this.phone = phone;
this.address = address;
this.email = email;
}
或者使用getter和setter的方式去写一个实现类:
public class Person {
private String name; //必须
private int age; //必须
private int sex; //必须
private float height;
private String phone;
private String address;
private String email;
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 getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
先说说这两种方式的优劣:
第一种在参数不多的情况下,是比较方便快捷的,一旦参数多了,代码可读性大大降低,并且难以维护,对调用者来说也造成一定困惑;
第二种可读性不错,也易于维护,但是这样子做对象会产生不一致的状态,当你想要传入全部参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实Person对象并没有创建完成,另外,这个Person对象也是可变的,不可变类所有好处都不复存在。
所以有没有更好地方式去实现它呢,那就是接下来要理解的Builder模式了。
以下介绍的Builder模式其实是Builder模式的衍生模式,他与最经典Builder模式有区别,因为现在很少使用经典Builder模式,基本都在使用Builder衍生模式(链式调用),所以经典Builder模式本人也没怎么研究,有兴趣的童鞋可以自行研究下设计模式之四 --- 建造(Builder)模式
新撸出来的代码:
public class Person {
private final String name; //必须
private final int age; //必须
private final int sex; //必须
private final float height;
private final String phone;
private final String address;
private final String email;
private Person(PersonBuilder builder){
this.name = builder.name;
this.age = builder.age;
this.sex = builder.sex;
this.height = builder.height;
this.phone = builder.phone;
this.address = builder.address;
this.email = builder.email;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSex() {
return sex;
}
public float getHeight() {
return height;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public String getEmail() {
return email;
}
public static class PersonBuilder{
private final String name; //必须
private final int age; //必须
private final int sex; //必须
private float height;
private String phone;
private String address;
private String email;
public PersonBuilder(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public PersonBuilder height(float height){
this.height = height;
return this;
}
public PersonBuilder phone(String phone){
this.phone = phone;
return this;
}
public PersonBuilder address(String address){
this.address = address;
return this;
}
public PersonBuilder email(String email){
this.email = email;
return this;
}
public Person build(){
return new Person(this);
}
}
}
从上面的代码中我们可以看到,我们在PersonBuilder
类里定义了一份与Person
类一模一样
的变量,通过一系列的成员函数进行设置属性值,但是返回值都是this
,也就是都是PersonBuilder对象
,最后提供了一个build
函数用于创建
Person对象,返回的是Person
对象,对应的构造函数在Person类中进行定义,也就是构造函数的入参
是PersonBuilder
对象,然后依次对自己的成员变量进行赋值,对应的值都是PersonBuilder
对象中的值。此外PersonBuilder类中的成员函数返回Builder对象自身的另一个作用就是让它支持链式调用,使代码可读性大大增强。
Builder模式需要有几个注意的点:
- Person类的构造方法必须是
私有
的,调用者不能直接创建
Person对象 - Person类的属性是不可变的,所有的属性都要添加
final
修饰符,并且在构造方法中设置了值,并且对外只提供getters
方法。 - PersonBuilder的内部类构造方法中
只接收必传的参数
,并且必传的参数使用final
修饰符。
调用方式
new Person.PersonBuilder("张三", 18, 1)
.address("北京市XXX")
.email("[email protected]")
.height(175.5f)
.phone("1234556")
.build();
相比起前面通过构造函数和setter/getter方法两种方式,可读性更强。唯一可能存在的问题就是会产生多余的Builder对象,消耗内存。然而大多数情况下我们的Builder内部类使用的是静态修饰的(static),所以这个问题也没多大关系。
怎么样Builder设计模式会使你的代码看上去高大上一些呢?
Android Studio对Builder模式的支持
AS 对Builder这种常用的设计模式提供了支持,方便我们的代码编写,在Plugins中搜索Builder,会出现一个InnerBuilder
的插件,
在编写好类和属性之后,右键generate选择Builder,选择属性
确定之后就会生成Builder模式下的代码,当然生成的代码和我们希望的还是有些不同,只需要进行小小的修改就可以了,还是很方便的!
总结
优点
- 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
- 建造者独立,容易扩展;
缺点
- 会产生多余的Builder对象,消耗内存;
- 对象的构建过程暴露。