重修设计模式-创建型-建造者模式

发布于:2024-09-18 ⋅ 阅读:(10) ⋅ 点赞:(0)

重修设计模式-创建型-建造者模式

允许用户通过链式调用方法来逐步构建复杂对象,让复杂对象的构建与它的表示分离,即对象的表示和对象的构造过程解耦。

建造者模式的原理和实现非常简单,重点在于复杂对象的构建过程和定制化。具体实现中,通常会定义一个 Builder 类,它拥有产品类的所有属性,并定义一系列链式调用的 set 方法,用于修改产品类属性,最后通过 build() 方法集中校验并创建产品类对象。

举个例子,Android 中 AlertDialog 是一个确认弹窗,其中有这样几个属性:

  • title:标题,必填。
  • message:展示内容,选填。
  • positiveButton:确认按钮文字,选填。
  • positiveButtonClick:确认按钮点击事件,选填,如果 positiveButton 有值则为必填。
  • cancelable:是否可取消,选填。

通过建造者模式实现如下:

class CAlertDialog private constructor(builder: Builder) {
    private val title: String = builder.title
    private val message: String? = builder.message
    private val positiveButton: String? = builder.positiveButton
    private val positiveButtonClick: OnClickListener? = builder.positiveButtonClick
    private val cancelable: Boolean = builder.cancelable

    fun show() {
        println("CAlertDialog(title=$title, message=$message, positiveButton=$positiveButton, positiveButtonClick=$positiveButtonClick, cancelable=$cancelable)")
    }

    //定义建造者
    class Builder {
        var title: String = ""
        var message: String? = null
        var positiveButton: String? = null
        var positiveButtonClick: OnClickListener? = null
        var cancelable: Boolean = true

        fun setTitle(title: String): Builder {
            this.title = title
            return this
        }

        fun setMessage(message: String): Builder {
            this.message = message
            return this
        }

        //使用Kotlin apply函数优化代码
        fun setPositiveButton(text: String) = this.apply { this.positiveButton = text }

        fun setPositiveButtonClick(click: OnClickListener) =
            this.apply { this.positiveButtonClick = click }

        fun setCancelable(cancelable: Boolean) = this.apply { this.cancelable = cancelable }

        fun build(): CAlertDialog {
            //校验依赖关系
            if (positiveButton?.isNotEmpty() == true) {
                if (positiveButtonClick == null) throw Throwable("请设置点击事件!")
            }
            return CAlertDialog(this)
        }
    }
}

使用时:

val dialog = CAlertDialog.Builder()
    .setTitle("标题")
    .setMessage("消息")
    .setPositiveButton("确定")
    .setPositiveButtonClick { v -> println("确定事件") }
    .setCancelable(true)
    .build()
dialog.show()

可以看到调用处的代码非常简洁,可以通过链式调用配置自定义属性,并在 build 方法中校验和创建对象,还避免了对象创建中的中间状态。不过 CAlertDialog 中的属性在 Builder 又定义了一遍,造成了代码重复,这也是建造者模式的缺点。

这种方式和直接设置产品类的 set 方法差异在什么地方呢?下面通过 set 方式实现一下上面例子。

//必填属性通过构造方法传入,就不用校验了
class CAlertDialog constructor(private var title: String) {
    private var message: String? = null
    private var positiveButton: String? = null
    private var positiveButtonClick: OnClickListener? = null
    private var cancelable: Boolean = true

    fun setMessage(message: String) = this.apply { this.message = message }

    fun setPositiveButton(text: String) = this.apply { this.positiveButton = text }

    fun setPositiveButtonClick(click: OnClickListener) = this.apply { this.positiveButtonClick = click }

    fun setCancelable(cancelable: Boolean) = this.apply { this.cancelable = cancelable }

    fun show() {
        println("CAlertDialog(title=$title, message=$message, positiveButton=$positiveButton, positiveButtonClick=$positiveButtonClick, cancelable=$cancelable)")
    }
}

调用处:

val dialog = CAlertDialog1("标题")
    .setMessage("消息")
    .setPositiveButton("确定")
    .setPositiveButtonClick { v -> println("确定事件") }
    .setCancelable(true)
dialog.show()

这里为产品类的一系列 set 方法也增加了返回自身,达到链式调用的目的,调用同样简洁。但实现过程中属性间依赖关系无法统一校验了,且 set 方法一定是暴露的,就意味着外部能随时修改,不能达到创建后对象不可变的目的了。

建造者模式 VS set方式:

建造者模式

  • 优点:
    • 支持链式调用
    • 支持统一校验,符合早抛出,晚捕获的异常处理原则(在发现错误时,应尽早抛出异常,避免错误扩散。有助于定位问题,提高程序的健壮性)
    • 支持对象创建后属性不可变
    • 一次性构建对象,避免无效的中间状态
  • 缺点:
    • 代码重复,产品类属性需要在建造者中重新定义一遍。
    • 如果产品类内部变化不大,使用建造者模式会增加复杂性。

set 方式

  • 优点:
    • 支持链式调用
    • 实现简单,无重复代码
  • 缺点:
    • 不支持属性依赖关系的统一校验
    • 必填对象过多时,构造方法中参数冗长
    • 不支持不可变对象
    • 有短暂的中间状态

如果不在意对象是否可变和短暂无效状态,set 方式也是可以使用的,毕竟 Builder 中的重复代码和建造者对象也会造成一些损耗,具体还是要视需求复杂程度而定,在软件编码原则和需求契合度之间做折中。

建造者与工厂模式区别:

  • 建造者关注对象的创建过程,通过一系列复杂步骤创建对象,每个步骤可以独立的改变对象的状态;工厂模式关注对象的创建结果,隐藏对象的创建细节。
  • 建造者侧重对象的“定制化”,工厂侧重对象的“规范化”

传统建造者模式

传统建造者模式是在链式调用基础上,通过模板模式进一步封装出指挥者(Director)抽象建造者(Builder)具体建造者(ConcreteBuilder),以便批量的创建出相同的复杂对象,这种方式封装程度更高,更加遵循设计模式的一些原则。不过任何设计模式都不能生搬硬套,还是那句话,在享受使用设计模式所带来的便捷性的同时,也不能被其所束缚。就建造者模式而言,在我客户端开发的生涯中很少遇到传统的实现方式,更多遇到的还是上面的 Builder + 链式调用的方式,比如 OkhttpClient 、AlertDialog 等。


网站公告

今日签到

点亮在社区的每一天
去签到