Pregunta ¿Cómo implementar el patrón Builder en Kotlin?


Hola, soy un novato en el mundo de Kotlin. Me gusta lo que veo hasta ahora y comencé a pensar en convertir algunas de nuestras bibliotecas que usamos en nuestra aplicación de Java a Kotlin.

Estas bibliotecas están llenas de Pojos con setters, getters y clases de Builder. Ahora busqué en Google para encontrar cuál es la mejor manera de implementar Builders en Kotlin pero no tuve éxito.

2da Actualización: ¿La pregunta es cómo escribir un patrón de diseño de Constructor para un pojo simple con algunos parámetros en Kotlin? El siguiente código es mi intento escribiendo código java y luego usando el eclipse-kotlin-plugin para convertir a 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
        }
    }
}

73
2018-03-21 20:19


origen


Respuestas:


Primero y ante todo, en la mayoría de los casos no es necesario usar constructores en Kotlin porque tenemos argumentos predeterminados y con nombre. Esto te permite escribir

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

y úsalo así:

val car = Car(model = "X")

Si desea usar constructores de forma definitiva, puede hacerlo de la siguiente manera:

Hacer que el constructor companion object no tiene sentido porque objects son singletons En su lugar, declararlo como una clase anidada (que es estática por defecto en Kotlin).

Mueva las propiedades al constructor para que el objeto también pueda ser instanciado de la manera habitual (haga que el constructor sea privado si no debería) y use un constructor secundario que tome un constructor y delegue al constructor primario. El código se verá de la siguiente manera:

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

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

Este código se puede acortar adicionalmente usando un constructor 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)
    }
}

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

Si se requieren algunos valores y no tienen valores predeterminados, debe colocarlos en el constructor del generador y también en el build método que acabamos de definir:

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

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


158
2018-03-22 09:48



Debido a que estoy usando la biblioteca de Jackson para analizar objetos desde JSON, necesito tener un constructor vacío y no puedo tener campos opcionales. Además, todos los campos deben ser mutables. Entonces puedo usar esta agradable sintaxis que hace lo mismo que el patrón de Constructor:

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

8
2018-06-29 11:41



Personalmente, nunca he visto un constructor en Kotlin, pero tal vez sea solo yo.

Toda validación que uno necesita sucede en el init bloquear:

class Car(val model: String,
          val year: Int = 2000) {

    init {
        if(year < 1900) throw Exception("...")
    }
}

Aquí me tomé la libertad de adivinar que en realidad no quería model y year ser cambiante Además, esos valores predeterminados no parecen tener sentido (especialmente null para name) pero dejé uno para fines de demostración.

Una opinión: El patrón de generador utilizado en Java como medio para vivir sin parámetros nombrados. En idiomas con parámetros nombrados (como Kotlin o Python), es una buena práctica tener constructores con largas listas de parámetros (tal vez opcionales).


7
2018-03-22 05:00



He visto muchos ejemplos que declaran diversiones adicionales como constructores. Personalmente me gusta este enfoque. Ahorre esfuerzo para escribir constructores.

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

    }
}

Todavía no he encontrado una manera que pueda obligar a inicializar algunos campos en DSL, como mostrar errores en lugar de lanzar excepciones. Avísame si alguien sabe.


4
2018-06-06 02:03



Diría que el patrón y la implementación se mantienen prácticamente iguales en Kotlin. A veces puede omitirlo gracias a los valores predeterminados, pero para la creación de objetos más complicados, los constructores siguen siendo una herramienta útil que no se puede omitir.


0
2018-03-21 20:27



puedes usar el parámetro opcional en kotlin ejemplo:

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

entonces

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

0
2018-05-24 01:49



Para una clase simple, no necesita un constructor por separado. Puede hacer uso de argumentos de constructor opcionales como describió Kirill Rakhman.

Si tienes una clase más compleja, Kotlin proporciona una forma de crear Groovy style Builders / DSL:

Constructores de tipo seguro 

Aquí hay un ejemplo:

Ejemplo de Github - Constructor / Ensamblador 


0
2017-10-24 20:48



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

    }

}

0
2018-05-25 21:59