建造者模式
定义
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式在Android开发时会用到,比如AlertDialog.Builder,LuBan压缩, OkHttpUtils中均用到Builder。
Builder Pattern适用于构建复杂对象,所谓复杂对象就是包含很多成员属性的对象,下面直接以我在Android开发中自己写的一个工具类为例进行讲解。
实例讲解
先看下我是怎么使用的:
从上图中红框部分应该不难看出,我实现的工具类的功能是保存联系人,我为什么要封装Android中保存联系人这个功能呢?我们看下工具类源码就知道了,其内部涉及判断String是否为空,以及大量的数据库操作,很繁琐:
package com.example.geigei_android.util;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.text.TextUtils;
/**
* author:zjp
* date:2019/5/9
*/
public class JiPeng {
private String name;
private String organ;
private String organTitle;
private String mobilePhone;
private String email;
private String workPhone;
private String postal;
private JiPeng(Builder builder) {
this.name = builder.name;
this.organ = builder.organ;
this.organTitle = builder.organTitle;
this.mobilePhone = builder.mobilePhone;
this.email = builder.email;
this.workPhone = builder.workPhone;
this.postal = builder.postal;
}
public static Builder with(Context context) {
return new Builder(context);
}
private void save(Context context) {
ContentResolver resolver = context.getContentResolver();
//找到最后一条记录的id
@SuppressLint("Recycle") Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{"_id"},
null, null, null, null);
ContentValues temp = new ContentValues();
long newId = ContentUris.parseId(resolver.insert(ContactsContract.RawContacts.CONTENT_URI,
temp));
LogUtil.d("newId is " + newId);
LogUtil.d("name is " + this.name);
//插入一个联系人id
ContentValues dataValues = new ContentValues();
//插入移动电话数据
if (!TextUtils.isEmpty(this.mobilePhone)) {
//指定数据属于哪个联系人
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
//指定数据类型
dataValues.put(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
//数据
dataValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, this.mobilePhone);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
//插入工作电话数据
if (!TextUtils.isEmpty(this.workPhone)) {
dataValues.clear();
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
dataValues.put(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
dataValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, this.workPhone);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
//插入姓名数据
if (!TextUtils.isEmpty(this.name)) {
dataValues.clear();
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
dataValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
dataValues.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, this.name);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
//插入邮箱
if (!TextUtils.isEmpty(this.email)) {
dataValues.clear();
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
dataValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
dataValues.put(ContactsContract.CommonDataKinds.Email.DATA, this.email);
dataValues.put(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
//添加地址
if (!TextUtils.isEmpty(this.postal)) {
dataValues.clear();
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
dataValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
dataValues.put(ContactsContract.CommonDataKinds.StructuredPostal.DATA, this.postal);
dataValues.put(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
//添加公司
if (!TextUtils.isEmpty(this.organ)) {
dataValues.clear();
dataValues.put(ContactsContract.Data.RAW_CONTACT_ID, newId);
dataValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE);
dataValues.put(ContactsContract.CommonDataKinds.Organization.DATA, this.organ);
if (!TextUtils.isEmpty(this.organTitle))
dataValues.put(ContactsContract.CommonDataKinds.Organization.TITLE, this.organTitle);
dataValues.put(ContactsContract.CommonDataKinds.Organization.TYPE, ContactsContract.CommonDataKinds.Organization.TYPE_WORK);
resolver.insert(ContactsContract.Data.CONTENT_URI, dataValues);
}
}
public static class Builder {
private Context context;
private String name;
private String organ;
private String organTitle;
private String mobilePhone;
private String email;
private String workPhone;
private String postal;
Builder(Context context) {
this.context = context;
}
private JiPeng build() {
return new JiPeng(this);
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setOrgan(String organ) {
this.organ = organ;
return this;
}
public Builder setOrganTitle(String organTitle) {
this.organTitle = organTitle;
return this;
}
public Builder setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public Builder setWorkPhone(String workPhone) {
this.workPhone = workPhone;
return this;
}
public Builder setPostal(String postal) {
this.postal = postal;
return this;
}
public void save() {
build().save(context);
}
}
}
我们按照上图中的调用顺序一点一点来看Builder是怎么起作用的。
首先我调用with函数
//我的调用
JiPeng.with(this)
//对应工具源代码
public static Builder with(Context context) {
return new Builder(context);
}
with()函数是一个静态函数,其新建一个Builder,关于Builder的定义见上文代码部分。
然后又调用了一堆setXxx函数,这些函数本质上都是一样的,从上图可以看出,我们的调用格式是
//with之后直接跟.setName()
//也就是说我们后面的操作都是对with函数新建的Builder对象操作的
JiPeng.with(this)
.setName(......)
//工具类源代码
public Builder setName(String name) {
this.name = name;
return this;
}
工具类代码里的setName()很简洁,将需要保存的联系人姓名赋值给Builder的成员变量,同理后面的一堆setXxx()都是这么实现的
最后调用
//一堆setXxx后跟
.save()
//对应工具类源码
public void save() {
build().save(context);
}
//builder()函数
private JiPeng build() {
return new JiPeng(this);
}
//save函数较复杂,直接去上面看,这里只是为了理清楚调用顺序
//其实save函数才是保存联系人的关键,鼠标滑轮向上滚一滚看看save函数的实现
如果读者跟着我的思路来,并且看了上面的源码,你就会发现,其实Builder类成员变量和JiPeng类成员变量几乎一模一样,Builder的作用就是接收参数,接收完参数之后调用save函数,将Builder接收的参数传给JiPeng,然后才是保存联系人的主逻辑代码。
试想如果我们不用Builder Pattern来构建一个保存联系人的Utils应该怎么构建,我刚开始的大体思路是:
public static void saveContact(String name, String telephone, ......){
//TODO,保存联系人的主逻辑
}
可是这样面临几个问题:
- 参数的顺序,我可能需要接近6,7个参数,如果读者真正写过6,7参数的函数调用就会发现,参数之间的对号入座是很烦的,需要频繁看函数定义才能搞明白参数顺序。
- 如果有些参数为空,当然也可以传入NULL,然后在函数内进行判断,但是我看到Builder的实现时我放弃了这种不优雅的做法。
总结
优点:
- 客户端不需要知道产品内部组成的细节,只需要传参就OK了。
- 可以更加灵活的处理多参数问题,可以理解成将复杂产品的创建步骤分解在不同的方法中
缺点:
- 增加了冗余成分,上面代码中可以看出,Builder的成员变量和JiPeng的成员变量十分类似
- 如果产品的内部变化复杂, 可能会导致需要定义很多建造者模式来实现这些变化,导致系统复杂性大大提高。