只一篇就夠了·設(shè)計模式(4) - 建造者模式

建造者模式(Builder Pattern)簡化了構(gòu)建復(fù)雜對象的過程,除了簡化復(fù)雜的對象的構(gòu)建過程,最核心的思想是化整為零、按需分配。

先說如何簡化構(gòu)建過程,建造者模式就像是Google的模塊化手機(jī),通過各個零件的定制來完成一部手機(jī),比如,我可以裝兩個攝像頭,或者把添加多一塊電池,只是換個模塊就能完成。

化整為零、按需分配說的是如果要實現(xiàn)一個多變的對象,把這個多變對象的整體,分解成一小塊一小塊的,然后組裝起來,還能按照需求決定哪些需要定制,怎么定制,比如,Android或者Ios中常用的DialogNotification等。

類圖

類圖不是目的,僅僅幫助理解

[圖片上傳失敗...(image-b3bc3-1527174228599)]

IBuilder是一個建造者接口,規(guī)范了建造的內(nèi)容,可以有很多實現(xiàn),比如,BuilderABuilderB,有一個重點就是化整為零的產(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ī)的CPURAMNexus5一樣,那賦值的代碼就重復(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)封裝到了IBuilderDirector

和工廠模式的區(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)建過程。

??查看更多??

不登高山,不知天之高也;不臨深溪,不知地之厚也
感謝指點、交流、喜歡

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容