Builder Design Pattern in Kotlin

Posted on By ᵇᵒ

优雅高效的Kotlin构造者模式

fun main() {
    val foo = Foo.build {
        bar = 1024
    }
    println("foo.bar=${foo.bar}")
}

class Foo private constructor(builder: Builder) {
    val bar: Int = builder.bar

    companion object {
        fun build(block: Builder.() -> Unit = {}) = Foo(Builder(block))
    }

    class Builder internal constructor(block: Builder.() -> Unit) {
        var bar: Int = 0

        init {
            block()
        }
    }
}

更极端的方式

fun main() {
    val foo = Foo {
        bar = 1024
    }
    println("foo.bar=${foo.bar}")
}

class Foo private constructor(builder: Builder) {
    val bar: Int = builder.bar

    companion object {
        operator fun invoke(block: Builder.() -> Unit = {}) = Foo(Builder(block))
    }

    class Builder internal constructor(block: Builder.() -> Unit) {
        var bar: Int = 0

        init {
            block()
        }
    }
}

该方式通过重载invoke运算符,使得调用代码更加精简,但是少了build字段导致可读性降低(很难一眼看出这是构造者模式)。 有利有弊吧,个人项目我更喜欢后一种,团体项目推荐使用前者。

Java的调用方式,以前一种为例(后一种只需要将build替换为invoke):

    Foo foo = Foo.Companion.build(new Function1<Foo.Builder, Unit>() {
        @Override
        public Unit invoke(Foo.Builder builder) {
            builder.setBar(1024);
            return Unit.INSTANCE;   // 也可以直接 return null
        }
    });

如果是Java 8及以上,还可以使用lambda的方式调用:

    Foo foo = Foo.Companion.build(builder -> {
        builder.setBar(1024);
        return null;
    });

The hack way

data class + val property + default value:

fun main() {
    val foo = Foo(hehe = "heihei")
    print("foo=$foo")
}

data class Foo(
    val haha: String = "haha",
    val hoho: String = "hoho",
    val hehe: String = "hehe"
)

一直觉得 data class 是 kotlin 挺废物的一个设计,直到最近突然悟到可以用来完美替代构造者模式。 该方式唯一的缺点就是在 java 里调用,由于无法缺省默认值导致设置属性非常麻烦。