13.好菜每回味都不同 - 建造者模式 (大話設計模式Kotlin版)

情景

小明的難堪

  • 小明晚上下班去大排檔喫宵夜,點了一份炒麪,味道不夠估計是忘了放鹽;喫着不盡興,再點了一份炒飯,結果卻太鹹了。小明真難!小明感嘆,這檔口以前經常光顧,爲什麼今天這麼難喫 ?可能換了廚師了吧,或者廚師今晚心情不好哈哈。吃了幾家肯德基與麥當勞,也沒見雞腿堡口味有很大差異呀!KFC 與 McDonald 在我國比許多中式快餐都成功,原因是他們有一套完整規範的工作流程,原料放多少,加熱幾分鐘,放幾克鹽,都有嚴格規定;這條流程是每個店面都嚴格遵照執行的。許多餐館就做不到這點,比如桂林米粉,在城市A喫與在城市B喫的味道會不一樣。因爲地方習慣與廚師的經驗決定了這碗米粉的口感。喫得爽還是喫得難受都依賴於廚師。
  • 在設計模式的原則中, 依賴倒轉原則:抽象不應該依賴於細節,細節應該依賴於抽象。菜的口感算是一種抽象,具體依賴於廚師這個細節,廚師多放、少放鹽直接都影響菜的口感;而 KFC 與 McDonald 它們製作事物都嚴格遵照一種抽象的工作流程,具體放什麼配料,烘焙多長時間等細節都依賴這個抽象。
  • 好喫的食物都依賴於工作流程,說到底還算是細節呀。去 KFC 消費我們不會在意他們的工作流程,我們更關心它是否好喫,這套流程是經過反覆試驗得出來的,會比較固定,假如要出一款新漢堡🍔 ,只是配料不同,流程走的過程仍是不變的。
  • 美食製作過程需嚴格遵照幾道工序,缺一不可;假若忘了放鹽,那將變得難以下嚥!

建造小人

爲了理解流程的抽象,編寫一個程序來畫小人。
要求:有頭、身體、雙手、雙腳

  • 繪畫程序在Android Studio中編寫PersonThinView的自定義View
/**
 * @create on 2020/5/27 22:41
 * @description 構建瘦的小人
 * @author mrdonkey
 */
class PersonThinView(context: Context, attr: AttributeSet) : View(context, attr) {
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //新建畫筆
        val paint = Paint().apply {
            isAntiAlias = true//設置抗鋸齒
            style = Paint.Style.STROKE//空心
            strokeWidth = 2.0f//線條粗細
        }
        canvas.drawCircle(500f, 200f, 100f, paint)//頭部
        canvas.drawRect(RectF(450f,300f,550f,600f),paint)//瘦身體
        canvas.drawLine(450f,300f,350f,600f,paint)//左手
        canvas.drawLine(550f,300f,650f,600f,paint)//右手
        canvas.drawLine(450f,600f,350f,900f,paint)//左腳
        canvas.drawLine(550f,600f,650f,900f,paint)//右腳
    }
}

預覽畫出來瘦的人:
瘦的人

  • 如果要再畫一個胖的人呢?簡單!換個胖身體不就行啦~
 canvas.drawCircle(500f, 200f, 100f, paint)//頭部
 canvas.drawOval(RectF(420f, 300f, 570f, 600f), paint)//胖身體
 canvas.drawLine(450f,300f,350f,600f,paint)//左手
 canvas.drawLine(550f,300f,650f,600f,paint)//右手
 canvas.drawLine(450f,600f,350f,900f,paint)//左腳

預覽:
胖的人

  • 咦,少畫了一條腿🦵。
canvas.drawLine(550f, 600f, 650f, 900f, paint)//右腳
  • 看吧,這就和做菜一樣,少放了鹽,導致美味的菜品變得無趣。如果畫一個健全的人,但卻少了條腿,這是肯定是不合格的。現在繪畫小人的代碼都寫在同一個類中,假如其他地方需要這個繪製小人的程序怎麼辦?

建造小人二

  • 簡單!將瘦、胖兩個分離,新建兩個類,一個是瘦人類,一個是胖人類,不管是誰都可以盡情的調用啦!
  • 瘦人的構建PersonThinBuilder
/**
 * @create on 2020/6/1 22:58
 * @description 構建小人二
 * @author mrdonkey
 */
class PersonThinBuilder constructor(private val paint: Paint, private val canvas: Canvas) {
    /**
     * 建造方法
     */
    fun build(){
        canvas.drawCircle(500f, 200f, 100f, paint)//頭部
        canvas.drawRect(RectF(450f,300f,550f,600f),paint)//瘦身體
        canvas.drawLine(450f,300f,350f,600f,paint)//左手
        canvas.drawLine(550f,300f,650f,600f,paint)//右手
        canvas.drawLine(450f,600f,350f,900f,paint)//左腳
        canvas.drawLine(550f,600f,650f,900f,paint)//右腳
    }
}
  • 胖人的構建 PersonFatBuilder
/**
 * @create on 2020/6/1 22:58
 * @description 構建小人二
 * @author mrdonkey
 */
class PersonFatBuilder constructor(val paint: Paint, val canvas: Canvas) {
    /**
     * 建造方法
     */
    fun build(){
        canvas.drawCircle(500f, 200f, 100f, paint)//頭部
        canvas.drawOval(RectF(400f,300f,600f,600f),paint)//胖身體
        canvas.drawLine(450f,300f,350f,600f,paint)//左手
        canvas.drawLine(550f,300f,650f,600f,paint)//右手
        canvas.drawLine(450f,600f,350f,900f,paint)//左腳
        canvas.drawLine(550f,600f,650f,900f,paint)//右腳
    }
}
  • 客戶端使用
       //新建畫筆
        val paint = Paint().apply {
            isAntiAlias = true//設置抗鋸齒
            style = Paint.Style.STROKE//空心
            strokeWidth = 2.0f//線條粗細
        }
        //構建瘦人
        val personThinBuilder =PersonThinBuilder(paint,canvas)
        personThinBuilder.build()
        //構建胖人
        val personFatBuilder =PersonFatBuilder(paint,canvas)
        personFatBuilder.build()
  • 上面的寫法確實是可以複用兩個畫小人的程序,但是忘了放鹽的細節問依然存在。比如現在讓你加一個高的人,你可能會因爲大意,又讓它少了一條腿🦵!。最好的解決方式是,規定凡是建造的小人,都必須有頭和身體、以及雙手雙腳。

建造者模式簡單應用

  • 通過上面例子可以發現,構造小人的‘過程’是穩定的,需要頭、身體、雙腳雙腿,而具體建造細節是不同的,比如有胖有瘦有高有矮。而對於使用者來說,他們不需要關心建造過程,只需要告訴程序,我需要構建一個胖小人。因此我們需要使用一個設計模式來解決這個問題。
  • 建造者模式: 將一個複雜對象的構建與它的表示分離,使得同樣的建造過程可以創建不同的表示意圖。使用建造者模式,用戶不需要關心過程和細節只需要指定建造的類型就可以得到它們。

下面我們應用建造者模式來解決建造小人的問題。

設計分析

  1. 建造過程是抽象,無論胖瘦人都具備基本的頭、身體、雙手雙腳。(建造抽象類)
  2. 具體子類來實現不同的細節,瘦與胖的細節由子類來承擔。(建造具體子類)
  3. 建造過程也是固定的,先畫頭,再畫身體。。。(構造過程控制者)

程序設計

建造小人的建造者模式UML

PersonBuilder 建造人的抽象類

/**
 * @create on 2020/6/1 23:08
 * @description 抽象建造人的類 [paint]畫筆 [canvas]畫布
 * @author mrdonkey
 */
abstract class PersonBuilder(private val paint: Paint, private val canvas: Canvas) {

    abstract fun buildHead()//畫頭

    abstract fun buildBody()//畫身體

    abstract fun buildArmLeft()//畫左手

    abstract fun buildArmRight()//畫右手

    abstract fun buildLegLeft()//畫左腳

    abstract fun buildLegRight()//畫右腳

}

PersonThinBuilder 建造瘦人類

**
 * @create on 2020/6/1 23:12
 * @description 瘦人建造者,需要構建一個小人,必須要繼承[PersonBuilder],必須重寫這些抽象方法,否則編譯不通過
 * @author mrdonkey
 */
class PersonThinBuilder(private val paint: Paint, private val canvas: Canvas) :
    PersonBuilder(paint, canvas) {

    override fun buildHead() {
        canvas.drawCircle(500f, 200f, 100f, paint)//頭部
    }

    override fun buildBody() {
        canvas.drawRect(RectF(450f, 300f, 550f, 600f), paint)//瘦身體
    }

    override fun buildArmLeft() {
        canvas.drawLine(450f, 300f, 350f, 600f, paint)//左手
    }

    override fun buildArmRight() {
        canvas.drawLine(550f, 300f, 650f, 600f, paint)//右手
    }

    override fun buildLegLeft() {
        canvas.drawLine(450f, 600f, 350f, 900f, paint)//左腳
    }

    override fun buildLegRight() {
        canvas.drawLine(550f, 600f, 650f, 900f, paint)//右腳
    }

}

PersonFatBuilder 建造胖人類

/**
 * @create on 2020/6/1 23:12
 * @description 胖人建造者
 * @author mrdonkey
 */
class PersonFatBuilder(private val paint: Paint, private val canvas: Canvas) :
    PersonBuilder(paint, canvas) {

    override fun buildHead() {
        canvas.drawCircle(500f, 200f, 100f, paint)//頭部
    }

    override fun buildBody() {
        canvas.drawOval(RectF(400f,300f,600f,600f),paint)//胖身體
    }

    override fun buildArmLeft() {
        canvas.drawLine(450f, 300f, 350f, 600f, paint)//左手
    }

    override fun buildArmRight() {
        canvas.drawLine(550f, 300f, 650f, 600f, paint)//右手
    }

    override fun buildLegLeft() {
        canvas.drawLine(450f, 600f, 350f, 900f, paint)//左腳
    }

    override fun buildLegRight() {
        canvas.drawLine(550f, 600f, 650f, 900f, paint)//右腳
    }

}

PersonDirector 建造模式的指揮者,目的是把固定的建造過程在這裏完成,用戶就不需要知道了。而且,由於這個過程每一步都是一定要做到,那就不會出現缺胳膊少腿的情況。

/**
 * @create on 2020/6/1 23:17
 * @description 指揮者,用它來控制建造過程,也用它來隔離用戶與構造過程的關聯
 * [pb] 用戶告訴指揮者,我需要什麼樣的人
 * @author mrdonkey
 */
class PersonDirector(private val pb: PersonBuilder) {

    /**
     * 根據用戶的選擇建造小人
     */
    fun createPerson() {
        pb.buildHead()
        pb.buildBody()
        pb.buildArmLeft()
        pb.buildArmRight()
        pb.buildLegLeft()
        pb.buildLegRight()
    }
}

客戶端代碼:

        //構造者模式建造瘦人
        val personThinBuilder = PersonThinBuilder(paint, canvas)
        val personDirector = PersonDirector(personThinBuilder)
        personDirector.createPerson()
        //構造者模式建造胖人
        val personFatBuilder = PersonFatBuilder(paint, canvas)
        val director = PersonDirector(personThinBuilder)
        director.createPerson()

小結

  • 如果需要增加一個高個子與矮個子的類,只需要繼承PersonBuilder然後在建造身體這個細節裏,一個加高一點,一個則矮一些。
  • 建造者模式是逐步建造產品的,裏面建造細節的方法都是每個具體小人需要的(足夠普遍),否則不需要加固定的流程中。

建造者模式解析

建造者模式UML

建造模式的UML
簡要概括各類作用:

  • Builder:建造各個部分的抽象類,是爲了創建一個Product對象的各個部件指定的抽象接口。
  • ConcreteBuilder:實現Builder接口或抽象類的具體構造者,決定各個細節如何實現。
  • Product:由多個部件組成的具體產品。
  • Director:指揮者,使用Builder接口的對象,根據用戶需求構建對象。

應用場景

  • 主要用於創建一些複雜的對象,對象內部構建間的建造順序非常穩定的,但對象內部的構建通常又面臨着複雜的變化
  • 它使得構造代碼與表示細節的代碼分離,隱藏了該產品是如果組裝/建造的,若需要改變一個產品的表示細節,只需要再定義一個具體的構造者即可。

基本代碼

Product產品類

/**
 * @create on 2020/6/1 23:26
 * @description 類產品,由多個部件組成
 * @author mrdonkey
 */
class Product {
    private val components = arrayListOf<String>()//部件

    /**
     * 添加產品部件
     */
    fun add(component: String) {
        components.add(component)
    }

    /**
     * 列舉所有產品部件
     */
    fun show() {
        components.forEach {
            println("-----$it-----")
        }
    }
}

Builder 抽象建造者類


/**
 * @create on 2020/6/1 23:29
 * @description 抽象構造者,確定產品由兩個部分componentA與componentB組成,並聲明一個得到產品建造後結果的方法getResult
 * @author mrdonkey
 */
abstract class Builder {

    abstract fun builderComponentA()
    abstract fun builderComponentB()
    abstract fun getResult(): Product

}

ConcreteBuilder1 具體構造者1,建造產品1

/**
 * @create on 2020/6/1 23:35
 * @description 具體建造者類1 建造具體的兩個部件是部件A和部件B
 * @author mrdonkey
 */
class ConcreteBuilder1 : Builder() {

    private val product = Product()

    override fun builderComponentA() {
        product.add("component A")
    }

    override fun builderComponentB() {
        product.add("component B")
    }

    override fun getResult(): Product {
        return product
    }
}

ConcreteBuilder2 具體建造者2,建造產品2

/**
 * @create on 2020/6/1 23:35
 * @description 具體建造者類2 建造具體的兩個部件是部件 X 和 部件 Y
 * @author mrdonkey
 */
class ConcreteBuilder2 : Builder() {

    private val product = Product()

    override fun builderComponentA() {
        product.add("component X")
    }

    override fun builderComponentB() {
        product.add("component Y")
    }

    override fun getResult(): Product {
        return product
    }
}

Director 指揮者類

/**
 * @create on 2020/6/1 23:38
 * @description 指揮者類,用來指揮建造過程
 * @author mrdonkey
 */
class Director {

    fun construct(builder: Builder) {
        builder.builderComponentA()
        builder.builderComponentB()
    }
}

Cilent 客戶端代碼

/**
 * @create on 2020/6/1 23:40
 * @description 客戶端類
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            val director = Director()
            val concreteBuilder1 = ConcreteBuilder1()
            val concreteBuilder2 = ConcreteBuilder2()
            //指揮者用 ConcreteBuilder的方法來構造產品
            director.construct(concreteBuilder1)
            director.construct(concreteBuilder2)
            concreteBuilder1.getResult().show()
            concreteBuilder2.getResult().show()
        }
    }
}

小結

  • 建造者模式是在當創建複雜的對象的算法應獨立於該對象的組成部分以及它們的裝配方式時適用的模式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章