Builder 模式

Posted by Allen Vork on March 6, 2018

定义

将复杂对象的构建与表示分离,使得同样的构建过程创造不同的表示。人话:创建一个对象可能需要很多参数,但是并不是每个参数都是必须的,传入不同的参数可以产生不同的结果。

UML 图


即用户使用 Director 来通过 Builder 创建 Product(现在很少有人用 Director 而是直接通过 Builder 来构造)。

Builder 模式实例

// Product 类
class House {

    private var washingRoom: Boolean = false
    private var babyRoom: Boolean = false
    private var readingRoom: Boolean = false

    fun setWasingRoom(hasWasingRoom: Boolean = true) {
        washingRoom = hasWasingRoom
    }

    fun setHasBabyRoom(hasBabyRoom: Boolean = true) {
        babyRoom = hasBabyRoom
    }

    fun setHasReadingRoom(hasReadingRoom: Boolean = true) {
        readingRoom = hasReadingRoom
    }
}

// builder 接口    
interface IHouseBuilder {

    fun setHasWasingRoom(hasWashingRoom: Boolean = true)

    fun setHasBabyRoom(hasBabyRoom: Boolean = true)

    fun setHasReadingRoom(hasReadingRoom: Boolean = true)

    fun build() : House
}

// builder 具体构建类
class HouseBuilder : IHouseBuilder {
    private var house = House()
    
    override fun setHasWasingRoom(hasWashingRoom: Boolean) {
        house.setWasingRoom(hasWashingRoom)
    }

    override fun setHasBabyRoom(hasBabyRoom: Boolean) {
        house.setHasBabyRoom(hasBabyRoom)
    }

    override fun setHasReadingRoom(hasReadingRoom: Boolean) {
        house.setHasBabyRoom(hasReadingRoom)
    }

    override fun build(): House {
        return house
    }
}

// House 的组装者
class HouseDirector {
    val houseBuilder: IHouseBuilder

    constructor(houseBuilder: IHouseBuilder) {
        this.houseBuilder = houseBuilder
    }

    fun buildBabyRoomAndReadingRoom(hasBabyRoom: Boolean, hasReadingRoom: Boolean) {
        houseBuilder.setHasReadingRoom(hasReadingRoom)
        houseBuilder.setHasBabyRoom(hasBabyRoom)
    }
}

//调用
val houseBuilder = HouseBuilder()
val houseDirector = HouseDirector(houseBuilder)
houseDirector.buildBabyRoomAndReadingRoom(false, true)
val house: House = houseBuilder.build();

我们有一个 House 可以配备卫生间,婴儿房和书房,通过 HouseBuilder 来构建房子,给房子配备任意几种设施,然后通过 HouseDirector 来真正组装一个房子。
不知道你们看完后是不是跟我一样的感觉:繁琐。我们可以简化一下,去掉 Director:

// Product 类
class House {

    private var washingRoom: Boolean = false
    private var babyRoom: Boolean = false
    private var readingRoom: Boolean = false

    fun setWasingRoom(hasWasingRoom: Boolean = true) {
        washingRoom = hasWasingRoom
    }

    fun setHasBabyRoom(hasBabyRoom: Boolean = true) {
        babyRoom = hasBabyRoom
    }

    fun setHasReadingRoom(hasReadingRoom: Boolean = true) {
        readingRoom = hasReadingRoom
    }
}

//builder 接口
interface IHouseBuilder {

    fun setHasWasingRoom(hasWashingRoom: Boolean = true)

    fun setHasBabyRoom(hasBabyRoom: Boolean = true)

    fun setHasReadingRoom(hasReadingRoom: Boolean = true)

    fun build() : House
}

// builder 具体构建类
class HouseBuilder : IHouseBuilder {
    private var house = House()

    override fun setHasWasingRoom(hasWashingRoom: Boolean): IHouseBuilder {
        house.setWasingRoom(hasWashingRoom)
        return this
    }

    override fun setHasBabyRoom(hasBabyRoom: Boolean): IHouseBuilder {
        house.setHasBabyRoom(hasBabyRoom)
        return this
    }

    override fun setHasReadingRoom(hasReadingRoom: Boolean): IHouseBuilder {
        house.setHasBabyRoom(hasReadingRoom)
        return this
    }

    override fun build(): House {
        return house
    }
}

//调用    
val house = HouseBuilder().setHasBabyRoom(true)
        .setHasReadingRoom(false)
        .build()

可以看出我们去掉了 Director 并且 Builder 的每个方法都会返回 IBuilder 对象用于链式调用。比第一种看起来舒服很多。下面再看另一种写法:

//builder 接口
interface IHouseBuilder {

    fun setHasWasingRoom(hasWashingRoom: Boolean = true): IHouseBuilder

    fun setHasBabyRoom(hasBabyRoom: Boolean = true): IHouseBuilder

    fun setHasReadingRoom(hasReadingRoom: Boolean = true): IHouseBuilder

    fun build() : House
}

// Product 类
class House {

    private var washingRoom: Boolean = false
    private var babyRoom: Boolean = false
    private var readingRoom: Boolean = false

    // set constructor protected to avoid being abused.
    protected constructor()

    fun setWasingRoom(hasWasingRoom: Boolean = true) {
        washingRoom = hasWasingRoom
    }

    fun setHasBabyRoom(hasBabyRoom: Boolean = true) {
        babyRoom = hasBabyRoom
    }

    fun setHasReadingRoom(hasReadingRoom: Boolean = true) {
        readingRoom = hasReadingRoom
    }

    // builder 具体构建类
    class HouseBuilder : IHouseBuilder {
        private var house = House()

        override fun setHasWasingRoom(hasWashingRoom: Boolean): IHouseBuilder {
            house.setWasingRoom(hasWashingRoom)
            return this
        }

        override fun setHasBabyRoom(hasBabyRoom: Boolean): IHouseBuilder {
            house.setHasBabyRoom(hasBabyRoom)
            return this
        }

        override fun setHasReadingRoom(hasReadingRoom: Boolean): IHouseBuilder {
            house.setHasBabyRoom(hasReadingRoom)
            return this
        }

        override fun build(): House {
            return house
        }
    }
}

//调用
val house = House.HouseBuilder().setHasBabyRoom(true)
        .setHasReadingRoom(false)
        .build()

第三种在第二种的基础上,将 Builder 的实现类作为 Product 类的内部类,然后将 Product 的构造函数的作用域降低到 protected。

问题

为什么将 Builder 类作为 Product 类的内部类?

我们知道,java 的内部类的最明显的好处是为 java 的单继承开后门。因为内部类也可以继承其余类,外部类可以将其余的需要继承的类分给自己的内部类,然后持有内部类的对象,这样就相当于间接实现了多继承。但这里凸显了内部类的另外一个好处。想一下,如果我们将 Builder 也作为一个普通的外部类的话,它要构建 Product,那么 Product 必须有 public 的构造函数,这样就可能被不熟悉的同学直接去构造 Product 而没有使用 Builder。把 Builder 类写成 Product 类的静态内部类后,把 Product 类的构造函数写成 protected 就可以解决这个问题。

我们可以看到 Builder 类和 Product 类中的函数都一样,我们为什么不直接调用 Product 类而是弄一个 Builder 去构建呢?

Product 类其实是一个很复杂的类,除了有 Builder 中的构建逻辑外,还有很多复杂的业务逻辑。通过 Builder 来构建的话,能将类之间的依赖建立在最小接口上,用户只用关心 Builder 类的几个接口就行了,不用去看 Product 类中复杂的逻辑。


下面我们来看下 Android 源码中 AlertDialog 是如何使用 Builder 模式的

note:为了方便大家理解,我将源码简化了一下:

class AlertDialog {
    val alertController: AlertController;

    protected constructor() {
        this.alertController = AlertController(getContext(), this, getWindow())
    }

    // AlertDialog 中也会有 Builder 中的 set 方法
    // 其目的是用户可以直接通过下面的 Builder 的 create() 方法拿到 AlertDialog,AlertDialog 可以自行再去
    // 设置(修改)Builder 中构建的参数
    fun setView(view: View) {
        alertController.setView(view)
    }

    //下面 Builder 的 show() 方法最终会调用到这个 onCreate() 方法
    protected fun onCreate(savedInstanceState: Bundle) {
        //该方法就是 AlertDialog 的具体构建逻辑,即将 Builder 最终传给 AlertController 的参数全部设置给 AlertDialog
        alertController.installContent()
    }

    class Builder {
        private val P: AlertController.AlertParams

        constructor() {
            P = AlertController.AlertParams()
        }

        // builder 模式的 set 方法
        fun setView(view: View): Builder {
            P.mView = view
            return this
        }

        fun create(): AlertDialog {
            val dialog = AlertDialog()
            //将 set 方法设置给 AlertParams 的值传递给 AlertController
            P.apply(dialog.alertController)
            return dialog
        }

        fun show(): AlertDialog {
            val dialog = create()
            //该方法最终会调用 AlertDialog 的 onCreate 方法
            dialog.show()
            return dialog
        }
    }
}

class AlertController {
    private val mDialog: AppCompatDialog
    private val mAlertDialogLayout: Int
    private var mView: View? = null
    private val mWindow: Window
    private val mContext: Context

    constructor(context: Context, di: AppCompatDialog, window: Window) {
        val a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
                R.attr.alertDialogStyle, 0)
        mWindow = window
        mContext = context
        mDialog = di //AlertDialog 对象最终是由 AlertController 持有,
        mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0)
    }

    //AlertParams 会调用这些方法来将参数传给 AlertController
    fun setView(view: View) {
        mView = view
    }

    //Builder 的 show() 方法会调用 Dialog 的 show() 方法,最终会调用
    //用 AlertParams 设置进来的参数构建 dialog,这里就是 AlertDialog 的具体构建逻辑
    fun installContent() {
        mDialog.setContentView(mAlertDialogLayout)
        ...
        val parentPanel: View = mWindow.findViewById(R.id.parentPanel)
        ...
    }


    //通过 Builder 的 set() 方法设置的参数都传到 AlertParams 中
    class AlertParams {
        var mView: View? = null

        //将 Builder 设置给 AlertParams 的值转交给 AlertController
        fun apply(alertController: AlertController) {
            if (mView != null) {
                alertController.setView(mView)
            }
        }
    }