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()
        }
    }
}

小结

  • 建造者模式是在当创建复杂的对象的算法应独立于该对象的组成部分以及它们的装配方式时适用的模式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值