Home Articles

如何在Kotlin中实现Builder模式?

Asked
Viewed 1219 times
93

嗨,我是Kotlin世界的新手 . 我喜欢到目前为止所看到的并开始考虑将我们在Java应用程序中使用的一些库转换为Kotlin .

这些库充满了带有setter,getter和Builder类的Pojos . 现在我用谷歌搜索了什么是在Kotlin中实施Builders的最佳方法,但没有成功 .

第二次更新:问题是如何在Kotlin中使用一些参数为简单的pojo编写Builder设计模式?下面的代码是我尝试编写java代码然后使用eclipse-kotlin-plugin转换为Kotlin .

class Car private constructor(builder:Car.Builder) {
    var model:String? = null
    var year:Int = 0
    init {
        this.model = builder.model
        this.year = builder.year
    }
    companion object Builder {
        var model:String? = null
        private set

        var year:Int = 0
        private set

        fun model(model:String):Builder {
            this.model = model
            return this
        }
        fun year(year:Int):Builder {
            this.year = year
            return this
        }
        fun build():Car {
            val car = Car(this)
            return car
        }
    }
}

10 Answers

  • 183

    我在Kotlin中使用以下代码实现了一个基本的Builder模式:

    data class DialogMessage(
            var title: String = "",
            var message: String = ""
    ) {
    
    
        class Builder( context: Context){
    
    
            private var context: Context = context
            private var title: String = ""
            private var message: String = ""
    
            fun title( title : String) = apply { this.title = title }
    
            fun message( message : String ) = apply { this.message = message  }    
    
            fun build() = KeyoDialogMessage(
                    title,
                    message
            )
    
        }
    
        private lateinit var  dialog : Dialog
    
        fun show(){
            this.dialog= Dialog(context)
            .
            .
            .
            dialog.show()
    
        }
    
        fun hide(){
            if( this.dialog != null){
                this.dialog.dismiss()
            }
        }
    }
    

    最后

    Java的:

    new DialogMessage.Builder( context )
           .title("Title")
           .message("Message")
           .build()
           .show();
    

    科特林:

    DialogMessage.Builder( context )
           .title("Title")
           .message("")
           .build()
           .show()
    
  • 10
    class Foo private constructor(@DrawableRes requiredImageRes: Int, optionalTitle: String?) {
    
        @DrawableRes
        @get:DrawableRes
        val requiredImageRes: Int
    
        val optionalTitle: String?
    
        init {
            this.requiredImageRes = requiredImageRes
            this.requiredImageRes = optionalTitle
        }
    
        class Builder {
    
            @DrawableRes
            private var requiredImageRes: Int = -1
    
            private var optionalTitle: String? = null
    
            fun requiredImageRes(@DrawableRes imageRes: Int): Builder {
                this.intent = intent
                return this
            } 
    
            fun optionalTitle(title: String): Builder {
                this.optionalTitle = title
                return this
            }
    
            fun build(): Foo {
                if(requiredImageRes == -1) {
                    throw IllegalStateException("No image res provided")
                }
                return Foo(this.requiredImageRes, this.optionalTitle)
            }
    
        }
    
    }
    
  • 7

    我已经看到很多例子宣称额外的乐趣作为建设者 . 我个人喜欢这种方法 . 省力写建设者 .

    package android.zeroarst.lab.koltinlab
    
    import kotlin.properties.Delegates
    
    class Lab {
        companion object {
            @JvmStatic fun main(args: Array<String>) {
    
                val roy = Person {
                    name = "Roy"
                    age = 33
                    height = 173
                    single = true
                    car {
                        brand = "Tesla"
                        model = "Model X"
                        year = 2017
                    }
                    car {
                        brand = "Tesla"
                        model = "Model S"
                        year = 2018
                    }
                }
    
                println(roy)
            }
    
            class Person() {
                constructor(init: Person.() -> Unit) : this() {
                    this.init()
                }
    
                var name: String by Delegates.notNull()
                var age: Int by Delegates.notNull()
                var height: Int by Delegates.notNull()
                var single: Boolean by Delegates.notNull()
                val cars: MutableList<Car> by lazy { arrayListOf<Car>() }
    
                override fun toString(): String {
                    return "name=$name, age=$age, " +
                            "height=$height, " +
                            "single=${when (single) {
                                true -> "looking for a girl friend T___T"
                                false -> "Happy!!"
                            }}\nCars: $cars"
                }
            }
    
            class Car() {
    
                var brand: String by Delegates.notNull()
                var model: String by Delegates.notNull()
                var year: Int by Delegates.notNull()
    
                override fun toString(): String {
                    return "(brand=$brand, model=$model, year=$year)"
                }
            }
    
            fun Person.car(init: Car.() -> Unit): Unit {
                cars.add(Car().apply(init))
            }
    
        }
    }
    

    我还没有找到一种方法可以强制某些字段在DSL中初始化,比如显示错误而不是抛出异常 . 如果有人知道,请告诉我 .

  • 4

    您可以在kotlin示例中使用可选参数:

    fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String = "default") {
        System.out.printf("parameter %s %d %d %s\n", p1, p2, p3, p4)
    }
    

    然后

    myFunc("a")
    myFunc("a", 1)
    myFunc("a", 1, 2)
    myFunc("a", 1, 2, "b")
    
  • 0

    首先,在大多数情况下,您不需要在Kotlin中使用构建器,因为我们有默认和命名参数 . 这使您可以写

    class Car(val model: String? = null, val year: Int = 0)
    

    并像这样使用它:

    val car = Car(model = "X")
    

    如果你绝对想要使用构建器,那么你可以这样做:

    使构建器成为 companion object 是没有意义的,因为 object 是单例 . 而是将其声明为嵌套类(默认情况下在Kotlin中是静态的) .

    将属性移动到构造函数,以便也可以以常规方式实例化对象(如果不应该将构造函数设为私有),并使用将构建器和委托作为主构造函数的辅助构造函数 . 代码如下:

    class Car( //add private constructor if necessary
            val model: String?,
            val year: Int
    ) {
    
        private constructor(builder: Builder) : this(builder.model, builder.year)
    
        class Builder {
            var model: String? = null
                private set
    
            var year: Int = 0
                private set
    
            fun model(model: String) = apply { this.model = model }
    
            fun year(year: Int) = apply { this.year = year }
    
            fun build() = Car(this)
        }
    }
    

    用法: val car = Car.Builder().model("X").builder()

    使用builder DSL可以另外缩短此代码:

    class Car (
            val model: String?,
            val year: Int
    ) {
    
        private constructor(builder: Builder) : this(builder.model, builder.year)
    
        companion object {
            inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
        }
    
        class Builder {
            var model: String? = null
            var year: Int = 0
    
            fun build() = Car(this)
        }
    }
    

    用法: val car = Car.build { model = "X" }

    如果某些值是必需的并且没有默认值,则需要将它们放在构建器的构造函数中以及我们刚刚定义的 build 方法中:

    class Car (
            val model: String?,
            val year: Int,
            val required: String
    ) {
    
        private constructor(builder: Builder) : this(builder.model, builder.year, builder.required)
    
        companion object {
            inline fun build(required: String, block: Builder.() -> Unit) = Builder(required).apply(block).build()
        }
    
        class Builder(
                val required: String
        ) {
            var model: String? = null
            var year: Int = 0
    
            fun build() = Car(this)
        }
    }
    

    用法: val car = Car.build(required = "requiredValue") { model = "X" }

  • 0

    因为我使用Jackson库来解析JSON中的对象,所以我需要一个空构造函数,而且我不能有可选字段 . 所有字段都必须是可变的 . 然后我可以使用这个与Builder模式相同的漂亮语法:

    val car = Car().apply{ model = "Ford"; year = 2000 }
    
  • 0

    我个人从未见过Kotlin的建筑师,但也许只是我 .

    所有验证都需要在 init 块中进行:

    class Car(val model: String,
              val year: Int = 2000) {
    
        init {
            if(year < 1900) throw Exception("...")
        }
    }
    

    在这里,我冒昧地猜测你并不真的想要 modelyear 可以改变 . 这些默认值似乎没有任何意义,(尤其是 null 对于 name )但我留下了一个用于演示目的 .

    An Opinion: Java中使用的构建器模式,表示没有命名参数的生存时间 . 在具有命名参数的语言(如Kotlin或Python)中,使用具有长列表(可能是可选的)的构造函数是一个好习惯 .

  • 0

    我会说Kotlin的模式和实现几乎保持不变 . 由于默认值,您有时可以跳过它,但是对于更复杂的对象创建,构建器仍然是一个无法省略的有用工具 .

  • 0

    对于一个简单的类,您不需要单独的构建器 . 您可以使用Kirill Rakhman描述的可选构造函数参数 .

    如果你有更复杂的类,那么Kotlin提供了一种创建Groovy样式的Builders / DSL的方法:

    Type-Safe Builders

    这是一个例子:

    Github Example - Builder / Assembler

  • 0

    人们现在应该检查Kotlin的Type-Safe Builders .

    使用所述对象创建方式将如下所示:

    html {
        head {
            title {+"XML encoding with Kotlin"}
        }
        // ...
    }
    

    一个很好的'in-action'用法示例是vaadin-on-kotlin框架,它使用了类型安全的构建器assemble views and components .

Related