目錄
一冯吓、場(chǎng)景分析
二、定義
三令漂、Builder模式變種-鏈?zhǔn)秸{(diào)用
四膝昆、經(jīng)典Builder模式
五丸边、用到Builder模式的例子
六、優(yōu)缺點(diǎn)
參考資料
最近在使用Retrofit和OkHttp框架的過(guò)程中發(fā)現(xiàn)創(chuàng)建相關(guān)對(duì)象時(shí)頻繁使用到了Builder模式荚孵,鏈?zhǔn)秸{(diào)用的方式讓代碼變得簡(jiǎn)潔妹窖、易懂,但自己也只是知其然而不知其所以然收叶,所以決定做個(gè)筆記加深下印象骄呼。
一、場(chǎng)景分析
在實(shí)際開(kāi)發(fā)中判没,往往會(huì)遇到需要構(gòu)建一個(gè)復(fù)雜的對(duì)象的代碼蜓萄,像這樣的:
public class User {
private String name; // 必傳
private String cardID; // 必傳
private int age; // 可選
private String address; // 可選
}
于是我們起手就是擼了一串這樣的代碼,譬如:
通過(guò)構(gòu)造函數(shù)的參數(shù)形式去寫(xiě)一個(gè)實(shí)現(xiàn)類(lèi)
User(String name);
User(String name, String cardID);
User(String name, String cardID,int age);
User(String name, String cardID,int age, String address);
又或者通過(guò)設(shè)置setter和getter方法的形式寫(xiě)一個(gè)實(shí)現(xiàn)類(lèi)
public class User {
private String name; // 必傳
private String cardID; // 必傳
private int age; // 可選
private String address; // 可選
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
先說(shuō)說(shuō)這兩種方式的優(yōu)劣:
第一種在參數(shù)不多的情況下澄峰,是比較方便快捷的嫉沽,一旦參數(shù)多了,代碼可讀性大大降低摊阀,并且難以維護(hù)耻蛇,對(duì)調(diào)用者來(lái)說(shuō)也造成一定困惑;
第二種可讀性不錯(cuò)胞此,也易于維護(hù)臣咖,但是這樣子做對(duì)象會(huì)產(chǎn)生不一致的狀態(tài),當(dāng)你想要傳入全部參數(shù)的時(shí)候漱牵,你必需將所有的setXX方法調(diào)用完成之后才行夺蛇。然而一部分的調(diào)用者看到了這個(gè)對(duì)象后,以為這個(gè)對(duì)象已經(jīng)創(chuàng)建完畢酣胀,就直接使用了刁赦,其實(shí)User對(duì)象并沒(méi)有創(chuàng)建完成,另外闻镶,這個(gè)User對(duì)象也是可變的甚脉,不可變類(lèi)所有好處都不復(fù)存在。
寫(xiě)到這里真想為自己最近封裝的表單控件捏一把汗铆农。牺氨。。所以有沒(méi)有更好地方式去實(shí)現(xiàn)它呢墩剖,那就是接下來(lái)要理解的Builder模式了猴凹。
二、定義
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離岭皂,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的展示郊霎。
Builder模式屬于創(chuàng)建型,一步一步將一個(gè)復(fù)雜對(duì)象創(chuàng)建出來(lái)爷绘,允許用戶(hù)在不知道內(nèi)部構(gòu)建細(xì)節(jié)的情況下书劝,可以更精細(xì)地控制對(duì)象的構(gòu)造流程进倍。
三、Builder模式變種-鏈?zhǔn)秸{(diào)用
代碼實(shí)現(xiàn)
public class User {
private final String name; //必選
private final String cardID; //必選
private final int age; //可選
private final String address; //可選
private final String phone; //可選
private User(UserBuilder userBuilder){
this.name=userBuilder.name;
this.cardID=userBuilder.cardID;
this.age=userBuilder.age;
this.address=userBuilder.address;
this.phone=userBuilder.phone;
}
public String getName() {
return name;
}
public String getCardID() {
return cardID;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public String getPhone() {
return phone;
}
public static class UserBuilder{
private final String name;
private final String cardID;
private int age;
private String address;
private String phone;
public UserBuilder(String name,String cardID){
this.name=name;
this.cardID=cardID;
}
public UserBuilder age(int age){
this.age=age;
return this;
}
public UserBuilder address(String address){
this.address=address;
return this;
}
public UserBuilder phone(String phone){
this.phone=phone;
return this;
}
public User build(){
return new User(this);
}
}
}
需要注意的點(diǎn):
- User類(lèi)的構(gòu)造方法是私有的庄撮,調(diào)用者不能直接創(chuàng)建User對(duì)象。
- User類(lèi)的屬性都是不可變的洞斯。所有的屬性都添加了final修飾符,并且在構(gòu)造方法中設(shè)置了值烙如。并且么抗,對(duì)外只提供getters方法蝇刀。
- Builder的內(nèi)部類(lèi)構(gòu)造方法中只接收必傳的參數(shù),并且該必傳的參數(shù)使用了final修飾符吞琐。
調(diào)用方式
new User.UserBuilder("Jack","10086")
.age(25)
.address("GuangZhou")
.phone("13800138000")
.build();
相比起前面通過(guò)構(gòu)造函數(shù)和setter/getter方法兩種方式,可讀性更強(qiáng)。唯一可能存在的問(wèn)題就是會(huì)產(chǎn)生多余的Builder對(duì)象站粟,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類(lèi)使用的是靜態(tài)修飾的(static)奴烙,所以這個(gè)問(wèn)題也沒(méi)多大關(guān)系。
關(guān)于線(xiàn)程安全
Builder模式是非線(xiàn)程安全的剖张,如果要在Builder內(nèi)部類(lèi)中檢查一個(gè)參數(shù)的合法性切诀,必需要在對(duì)象創(chuàng)建完成之后再檢查,
正確示例:
public User build() {
User user = new user(this);
if (user.getAge() > 120) {
throw new IllegalStateException(“Age out of range”); // 線(xiàn)程安全
}
return user;
}
錯(cuò)誤示例:
public User build() {
if (age > 120) {
throw new IllegalStateException(“Age out of range”); // 非線(xiàn)程安全
}
return new User(this);
}
四搔弄、經(jīng)典Builder模式
UML類(lèi)圖
Product : 產(chǎn)品抽象類(lèi)
Builder : 抽象Builder類(lèi)幅虑,規(guī)范產(chǎn)品組建,一般是由子類(lèi)實(shí)現(xiàn)具體的組建過(guò)程
ConcreteBuilder : 具體的Builder類(lèi)
Director : 統(tǒng)一組裝過(guò)程
Product角色
/**
* 用戶(hù)抽象類(lèi)
*/
public abstract class User {
protected String name;
protected String cardID;
protected int age;
protected String address;
public void setName(String name) {
this.name = name;
}
public abstract void setCardID();
public void setAge(int age) {
this.age = age;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [name ="+name+",cardID="+cardID+",age="+age+"," +
"address="+address+"]";
}
}
具體的Product類(lèi)
/**
* 具體的Product角色 SysUser
*/
public class SysUser extends User {
public SysUser() {
}
@Override
public void setCardID() {
cardID="10086"; //設(shè)置默認(rèn)ID
}
}
抽象Builder類(lèi)
public abstract class Builder {
public abstract void buildName(String name);
public abstract void buildCardID();
public abstract void buildAge(int age);
public abstract void buildAddress(String address);
public abstract User create();
}
具體的Builder類(lèi)
public class AccountBuilder extends Builder{
private User user=new SysUser();
@Override
public void buildName(String name) {
user.setName(name);
}
@Override
public void buildCardID() {
user.setCardID();
}
@Override
public void buildAge(int age) {
user.setAge(age);
}
@Override
public void buildAddress(String address) {
user.setAddress(address);
}
@Override
public User create() {
return user;
}
}
Director角色顾犹,負(fù)責(zé)構(gòu)造User
public class Director {
Builder mBuilder =null;
public Director(Builder builder){
this.mBuilder =builder;
}
public void construct(String name,int age,String address){
mBuilder.buildName(name);
mBuilder.buildCardID();
mBuilder.buildAge(age);
mBuilder.buildAddress(address);
}
}
測(cè)試代碼
public class Test{
public static void main(String args){
//構(gòu)建器
Builder builder=new AccountBuilder();
//Director
Director director=new Director(builder);
//封裝構(gòu)建過(guò)程:Jack倒庵,10086,25,GuangZhou
director.construct("Jack",25,"GuangZhou");
//打印結(jié)果
System.out.println("Info :" +builder.create().toString());
}
}
輸出結(jié)果
System.out: Info :User [name =Jack,cardID=10086,age=25,address=GuangZhou]
五、用到Builder模式的例子
- Android中的AlertDialog.Builder
private void showDialog(){
AlertDialog.Builder builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//TODO
}
});
builder.setNegativeButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//TODO
}
});
builder.create().show();
}
- OkHttp中OkHttpClient的創(chuàng)建
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(getCache())
.addInterceptor(new HttpCacheInterceptor())
.addInterceptor(new LogInterceptor())
.addNetworkInterceptor(new HttpRequestInterceptor())
.build();
- Retrofit中Retrofit對(duì)象的創(chuàng)建
Retrofit retrofit = new Retrofit.Builder()
.client(createOkHttp())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
可見(jiàn)在實(shí)際使用中蹦渣,均省略掉了Director角色,在很多框架源碼中貌亭,涉及到Builder模式時(shí)柬唯,大多都不是經(jīng)典GOF的Builder模式,而是選擇了結(jié)構(gòu)更加簡(jiǎn)單的后者圃庭。
六锄奢、優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 良好的封裝性失晴,使得客戶(hù)端不需要知道產(chǎn)品內(nèi)部實(shí)現(xiàn)的細(xì)節(jié)
- 建造者獨(dú)立,擴(kuò)展性強(qiáng)
缺點(diǎn):
- 產(chǎn)生多余的Builder對(duì)象拘央、Director對(duì)象涂屁,消耗內(nèi)存