建造者模式(
Builder Pattern
)簡化了構(gòu)建復(fù)雜對象的過程,除了簡化復(fù)雜的對象的構(gòu)建過程,最核心的思想是化整為零、按需分配。
先說如何簡化構(gòu)建過程,建造者模式就像是Google的模塊化手機(jī),通過各個零件的定制來完成一部手機(jī),比如,我可以裝兩個攝像頭,或者把添加多一塊電池,只是換個模塊就能完成。
化整為零、按需分配說的是如果要實現(xiàn)一個多變的對象,把這個多變對象的整體,分解成一小塊一小塊的,然后組裝起來,還能按照需求決定哪些需要定制,怎么定制,比如,Android或者Ios中常用的Dialog
、Notification
等。
類圖
類圖不是目的,僅僅幫助理解
[圖片上傳失敗...(image-b3bc3-1527174228599)]
IBuilder
是一個建造者接口,規(guī)范了建造的內(nèi)容,可以有很多實現(xiàn),比如,BuilderA
、BuilderB
,有一個重點就是化整為零的產(chǎn)品和Builder
之間是組合關(guān)系,有了建造者和產(chǎn)品我們就可以通過建造者來定制產(chǎn)品了,這時候Director
的作用就是規(guī)定構(gòu)造產(chǎn)品的順序或者一些固定的其他默認(rèn)屬性,比如構(gòu)建的時候依賴順序必須先是A后是B,那么可以通過Director
來控制,這里的Direcotr
和抽象工廠模式有點像。
聚合關(guān)系:對象和對象之間是
整體
和部分
的關(guān)系,部分
的生命周期可以超越整體
,這是弱關(guān)系。
組合關(guān)系:對象和對象之間是整體
和部分
的關(guān)系,部分
的生命周期不能超越整體
,這是強(qiáng)關(guān)系。
實例
先看看實例的類圖。
類圖不是目的,僅僅幫助理解
[圖片上傳失敗...(image-473fe6-1527174228599)]
還是以手機(jī)為例,規(guī)定一個手機(jī)產(chǎn)品的創(chuàng)建流程,用于流水線生產(chǎn),手機(jī)的生產(chǎn)需要生產(chǎn):主板、CPU、內(nèi)存、屏幕、外殼。現(xiàn)在要實現(xiàn)一部手機(jī),可以這樣做:
fun main(args: Array<String>)
{
val nexus5 = Phone("Google")
nexus5.cpu = "Google CPU"
nexus5.ram = "Google RAM"
nexus5.screen = "Google Screen"
nexus5.motherboard = "Google Motherboard"
nexus5.view = "Google Nexus5 View"
println(nexus5)
}
// Phone
/**
* 手機(jī)
* Created by Carlton on 2016/11/15.
*/
class Phone(val name: String)
{
/**
* cpu
*/
var cpu: String? = null
/**
* 內(nèi)存
*/
var ram: String? = null
/**
* 屏幕
*/
var screen: String? = null
/**
* 主板
*/
var motherboard: String? = null
/**
* 外觀
*/
var view: String? = null
override fun toString(): String
{
return "Phone(name='$name', cpu=$cpu, ram=$ram, screen=$screen, motherboard=$motherboard, view=$view)"
}
}
這樣,創(chuàng)建了一只Nexus5
的手機(jī),如果現(xiàn)在需要一只Nexus6
手機(jī)怎么做呢?重新創(chuàng)建一個Phone
實例,然后給屬性賦值。如果Nexus6
手機(jī)的CPU
、RAM
和Nexus5
一樣,那賦值的代碼就重復(fù)了,不方便重用了。如果還需要一個蘋果6手機(jī),又得重新去實例化Phone
對象,如果再建造一個蘋果7,CPU
、主板
都一樣,就會重復(fù)做很次這些操作,關(guān)鍵問題還不在這里,關(guān)鍵問題是暴露了產(chǎn)品的具體信息,這樣產(chǎn)品類就變得極其不穩(wěn)定,后期修改產(chǎn)品類的時候很難維護(hù),因為很多地方在修改屬性,如果使用建造者包裝一次,客戶端就不知道產(chǎn)品內(nèi)部的具體信息(只有建造者知道,這樣就控制了產(chǎn)品類出現(xiàn)的次數(shù)),后面修改產(chǎn)品類的時候就比較輕松。
還有一個問題是如果對構(gòu)造順序有嚴(yán)格的要求,比如必須先建主板才能建cpu那么,上面這種方式就不能控制。建造者的實現(xiàn):
fun main(args: Array<String>)
{
// 首先創(chuàng)建一個Google手機(jī)的建造者創(chuàng)建一個nexus5手機(jī)
val googleBuilder = GoogleBuilder()
googleBuilder.buildCpu("Google CPU")
googleBuilder.buildMotherboard("Google Motherboard")
googleBuilder.buildRam("Google RAM")
googleBuilder.buildScreen("Google Screen")
googleBuilder.buildView("Google View")
val director = Director(googleBuilder)
val nexus5 = director.build()
println(nexus5)
// 現(xiàn)在創(chuàng)建一個nexus6的手機(jī),還是用nexus5的建造者,屏幕和外觀不一樣
googleBuilder.buildScreen("Google Big Screen")
googleBuilder.buildView("Google Big View")
println(Director(googleBuilder).build())
}
// 打印
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Screen, motherboard=Google Motherboard, view=Google View)
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Big Screen, motherboard=Google Motherboard, view=Google Big View)
/**
* 手機(jī)Builder
* Created by Carlton on 2016/11/15.
*/
interface IPhoneBuilder
{
/**
* 定制CPU
*/
fun buildCpu(cpu: String?)
/**
* 定制內(nèi)容
*/
fun buildRam(ram: String?)
/**
* 定制屏幕
*/
fun buildScreen(screen: String?)
/**
* 定制主板
*/
fun buildMotherboard(motherboard: String?)
/**
* 定制視圖
*/
fun buildView(view: String?)
/**
* 創(chuàng)建
*/
fun create(): Phone
}
// Google手機(jī)建造者
/**
* 谷歌手機(jī)建造者
* Created by Carlton on 2016/11/15.
*/
class GoogleBuilder : IPhoneBuilder
{
override fun create(): Phone
{
return phone
}
val phone = Phone("Google")
override fun buildCpu(cpu: String?)
{
phone.cpu = cpu
}
override fun buildRam(ram: String?)
{
phone.ram = ram
}
override fun buildScreen(screen: String?)
{
phone.screen = screen
}
override fun buildMotherboard(motherboard: String?)
{
phone.motherboard = motherboard
}
override fun buildView(view: String?)
{
phone.view = view
}
}
// 組裝者
/**
* 組裝者
* Created by Carlton on 2016/11/16.
*/
class Director(val builder: IPhoneBuilder)
{
/**
* 順序建造
*/
fun build(cpu: String, ram: String, motherboard: String, screen: String, view: String): Phone
{
builder.buildMotherboard(motherboard)
builder.buildCpu(cpu)
builder.buildRam(ram)
builder.buildScreen(screen)
builder.buildView(view)
return builder.create()
}
/**
* 建造
*/
fun build(): Phone
{
return builder.create()
}
}
首先,客戶端創(chuàng)建了一個Google
手機(jī)的建造者,并且分別建造了各個部件,然后拿到組裝者去組裝,組裝的時候就可以按照一定的順序來組裝,或者在組裝的時候做一些其他事情,接來下讓建造者修改了其中兩個部件屏幕和外觀,然后造了一個新手機(jī)。這樣做可以輕易的替換建造者,而其他部分代碼不用修改來控制建造過程。
總結(jié)一下,建造者(IBuilder
)可以隱藏具體的產(chǎn)品建造過程,產(chǎn)品的消費者只需要拿到完整的產(chǎn)品,組裝者(Director
)可以控制產(chǎn)品組裝的流程,具體的產(chǎn)品的創(chuàng)造和實例化客戶端根本不關(guān)心。建造者也提供了很強(qiáng)的擴(kuò)展性,通過替換建造者或者修改某一個建造者,就能在背后影響產(chǎn)品的創(chuàng)造過程,而客戶端也就是消費者并不知道,建造者把業(yè)務(wù)需求表現(xiàn)的差異化實現(xiàn)封裝到了IBuilder
和Director
。
和工廠模式的區(qū)別
和工廠模式一樣都是輸入創(chuàng)建類型的設(shè)計模式,封裝創(chuàng)建過程給消費者,從類圖上可以看出來和抽象工廠模式很像,但是,之前說過,類圖只是參考,學(xué)習(xí)設(shè)計模式主要是學(xué)習(xí)其思路,在思路上抽象工廠模式是直接創(chuàng)建一個產(chǎn)品,及時的就把產(chǎn)品創(chuàng)造出來了,而建造者模式是先準(zhǔn)備和定制產(chǎn)品屬性,最后通過build()
或者create()
來創(chuàng)建一個產(chǎn)品。建造者的創(chuàng)建過程可以由客戶端來控制,在創(chuàng)建過程上比抽象工廠模式更加靈活,在概念上抽象工廠模式創(chuàng)建的是一個產(chǎn)品族,是一類整體,建造者模式中產(chǎn)品過程則是獨立的個體。
建造者的變種
建造者的核心在想在于創(chuàng)建產(chǎn)品,由很小的一些塊組成整體產(chǎn)品。所以一般情況下不需要使用標(biāo)準(zhǔn)的建造者格式,大多數(shù)時候建造順序不重要,這樣只需要一個Builder
類,連接口都可以不使用,這種情況在很多地方都應(yīng)用,舉個例子:
Java中的java.util.Calendar.Builder,是一個針對日歷實例的建造者
[圖片上傳失敗...(image-d31cc6-1527174228599)]
可以看到這里面有很多set
方法,這些方法就是在定制這個產(chǎn)品,你會發(fā)現(xiàn)不需要有任何的順序或者必須要調(diào)用,一個產(chǎn)品匹配一個建造者,主要作用是簡化了實例過程,因為需要設(shè)置的屬性太多了!最后使用build
方法生產(chǎn)一個Calendar
實例,這是典型的使用方式。
設(shè)計模式不要局限于形式,而在于思想
幾個其他的例子
用幾個簡單的例子來加深一下理解。
StringBuilder
在Java
中如果要對String
進(jìn)行操作盡量使用StringBuilder
,原因是String
的連接等操作會產(chǎn)生新的實例,它是一個不可變的對象,比如,String str = "abc" + "bcd";
這里內(nèi)存中會產(chǎn)生3個對象,"abc"、"bcd"
都是一個String
對象,然后連接后把引用給到str
。
那么,StringBuilder
在這里有什么作用呢?StringBuilder
里面是一個char
數(shù)組,如果用StringBuilder
來做應(yīng)該是這個樣子:
val stringBuilder = StringBuilder()
stringBuilder.append("abc")
stringBuilder.append("bcd")
val str = stringBuilder.toString()
同樣的都是拼接"acb"和"bcd"
如果使用StringBuilder
的話我們只會在toString()
的時候創(chuàng)建一個String
,不是每一次都去創(chuàng)建好一個產(chǎn)品,然后做操作,這樣就提高了性能:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
這里的toString()
跟build()、create()
是一個意思。用建造者模式,我們可以對產(chǎn)品各個情況先組建好,這里就是對字符各種操作先操作完成,最后一次輸出完整的字符串對象。
GsonBuilder
Gson我相信都用過,是一個Json轉(zhuǎn)對象,對象轉(zhuǎn)Json的庫,和FastJson一樣,前者是Google的,后者是阿里的。先看一段代碼:
……
public final class GsonBuilder
{
private Excluder excluder;
private LongSerializationPolicy longSerializationPolicy;
private FieldNamingStrategy fieldNamingPolicy;
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final List<TypeAdapterFactory> factories;
private final List<TypeAdapterFactory> hierarchyFactories;
private boolean serializeNulls;
private String datePattern;
private int dateStyle;
private int timeStyle;
private boolean complexMapKeySerialization;
private boolean serializeSpecialFloatingPointValues;
private boolean escapeHtmlChars;
private boolean prettyPrinting;
private boolean generateNonExecutableJson;
……
這里有很多很多很多的屬性,這些屬性關(guān)系到對Json的解析和處理方式,我們不可能每次解析Json的時候都去賦值這么多屬性,所以看看使用建造者模式如何規(guī)避這個問題,GsonBuilder
在實例化的時候預(yù)先了一些默認(rèn)值:
public GsonBuilder()
{
this.excluder = Excluder.DEFAULT;
this.longSerializationPolicy = LongSerializationPolicy.DEFAULT;
this.fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
this.instanceCreators = new HashMap();
this.factories = new ArrayList();
this.hierarchyFactories = new ArrayList();
this.dateStyle = 2;
this.timeStyle = 2;
this.escapeHtmlChars = true;
}
然后也提供了一些方法來修改部分屬性,也就是建造者方法,這樣可以通過GsonBuilder
來建造一個Gson
實例,而不用過多的去關(guān)注建造過程。當(dāng)所有的屬性都準(zhǔn)備完成后,一次性輸出產(chǎn)品:
public Gson create()
{
ArrayList factories = new ArrayList();
factories.addAll(this.factories);
Collections.reverse(factories);
factories.addAll(this.hierarchyFactories);
this.addTypeAdaptersForDate(this.datePattern, this.dateStyle, this.timeStyle, factories);
return new Gson(this.excluder, this.fieldNamingPolicy, this.instanceCreators, this.serializeNulls, this.complexMapKeySerialization, this.generateNonExecutableJson, this.escapeHtmlChars, this.prettyPrinting, this.serializeSpecialFloatingPointValues, this.longSerializationPolicy, factories);
}
Android中也有很多建造者的應(yīng)用比如:android.support.v4.app.NotificationCompat.Builder、android.support.v7.app.AlertDialog.Builder
,為什么使用建造者,建造者又有哪些缺點,通過這些實例自己能夠去理解才是最重要的。
總結(jié)
建造者模式和抽象工廠類似都是封裝了產(chǎn)品的建造過程,區(qū)別是建造者模式是構(gòu)建完后一次性輸出完整的產(chǎn)品,抽象工廠創(chuàng)建實例的時候,直接就輸出了完成的產(chǎn)品,相比之下,建造者可以定制和控制構(gòu)建過程,建造者也簡化了產(chǎn)品的創(chuàng)建過程。
不登高山,不知天之高也;不臨深溪,不知地之厚也
感謝指點、交流、喜歡