web-dev-qa-db-fra.com

Comment implémenter le pattern Builder dans Kotlin?

Bonjour, je suis un novice dans le monde Kotlin. J'aime ce que je vois jusqu'à présent et j'ai commencé à penser à convertir certaines de nos bibliothèques utilisées dans notre application de Java à Kotlin.

Ces bibliothèques regorgent de Pojos avec des setters, des getters et des classes Builder. Maintenant, j'ai cherché sur Google la meilleure façon d'implémenter les Builders à Kotlin, mais sans succès.

2ème mise à jour: La question est de savoir comment écrire un motif de conception Builder pour un simple pojo avec certains paramètres dans Kotlin? Le code ci-dessous est ma tentative en écrivant du code Java, puis en utilisant le plugin Eclipse-kotlin pour convertir en 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
        }
    }
}
98
Keyhan

Tout d’abord, dans la plupart des cas, vous n’avez pas besoin d’utiliser des générateurs dans Kotlin, car nous avons des arguments par défaut et des arguments nommés. Cela vous permet d'écrire

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

et l'utiliser comme ça:

val car = Car(model = "X")

Si vous voulez absolument utiliser des générateurs, voici comment vous pouvez le faire:

Faire de Builder un companion object n'a pas de sens car objects sont des singletons. À la place, déclarez-le en tant que classe imbriquée (qui est statique par défaut dans Kotlin).

Déplacez les propriétés vers le constructeur afin que l'objet puisse également être instancié de la manière habituelle (rendez le constructeur privé s'il ne le devrait pas) et utilisez un constructeur secondaire qui prend un constructeur et des délégués vers le constructeur principal. Le code ressemblera à ceci:

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

Usage: val car = Car.Builder().model("X").build()

Ce code peut être raccourci en utilisant un constructeur 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)
    }
}

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

Si certaines valeurs sont obligatoires et n'ont pas de valeurs par défaut, vous devez les placer dans le constructeur du générateur et également dans la méthode build que nous venons de définir:

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

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

201
Kirill Rakhman

Parce que j'utilise la bibliothèque Jackson pour analyser les objets à partir de JSON, il me faut un constructeur vide et je ne peux pas avoir de champs optionnels. De plus, tous les champs doivent être mutables. Ensuite, je peux utiliser cette syntaxe Nice qui fait la même chose que le modèle Builder:

val car = Car().apply{ model = "Ford"; year = 2000 }
10
David Vávra

Personnellement, je n'ai jamais vu de constructeur à Kotlin, mais c'est peut-être juste moi.

Toutes les validations nécessaires se produisent dans le bloc init:

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

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

Ici, j'ai pris la liberté de deviner que vous ne vouliez pas vraiment que model et year soient modifiables. De plus, ces valeurs par défaut semblent n'avoir aucun sens (en particulier null pour name), mais j'en ai laissé une à des fins de démonstration.

Un avis: Le modèle de construction utilisé en Java comme moyen de vivre sans paramètres nommés. Dans les langages avec des paramètres nommés (comme Kotlin ou Python), il est recommandé d’avoir des constructeurs avec de longues listes de paramètres (éventuellement optionnels).

7
voddan

J'ai vu de nombreux exemples qui déclarent des plaisirs supplémentaires en tant que constructeurs. Personnellement, j'aime cette approche. Épargnez vos efforts pour écrire aux constructeurs.

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

    }
}

Je n'ai pas encore trouvé de moyen qui puisse forcer l'initialisation de certains champs dans DSL, comme afficher des erreurs au lieu de générer des exceptions. Faites-moi savoir si quelqu'un sait.

4
Arst

Une approche consiste à faire quelque chose comme ceci:

class Car(
  val model: String?,
  val color: String?,
  val type: String?) {

    data class Builder(
      var model: String? = null,
      var color: String? = null,
      var type: String? = null) {

        fun model(model: String) = apply { this.model = model }
        fun color(color: String) = apply { this.color = color }
        fun type(type: String) = apply { this.type = type }
        fun build() = Car(model, color, type)
    }
}

Exemple d'utilisation:

val car = Car.Builder()
  .model("Ford Focus")
  .color("Black")
  .type("Type")
  .build()
4
Dmitrii Bychkov

Je suis en retard à la fête. J'ai également rencontré le même dilemme si je devais utiliser le modèle Builder dans le projet. Plus tard, après des recherches, j’ai réalisé que c’était absolument inutile puisque Kotlin fournit déjà les arguments nommés et les arguments par défaut.

Si vous avez vraiment besoin de la mettre en œuvre, la réponse de Kirill Rakhman est une réponse solide sur la manière de la mettre en œuvre de la manière la plus efficace. Une autre chose que vous pouvez trouver utile est https://www.baeldung.com/kotlin-builder-pattern vous pouvez comparer et contraster avec Java et Kotlin sur leur implémentation

1
Farruh Habibullaev

Pour un cours simple, vous n'avez pas besoin d'un constructeur séparé. Vous pouvez utiliser des arguments de constructeur facultatifs tels que décrits par Kirill Rakhman.

Si vous avez une classe plus complexe, Kotlin vous permet de créer des constructeurs/DSL de style Groovy:

Constructeurs de Type-Safe

Voici un exemple:

Exemple Github - Constructeur/Assembleur

1
Dariusz Bacinski

J'ai implémenté un modèle de base Builder dans Kotlin avec le code suivant:

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

Et enfin 

Java:

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

Kotlin:

DialogMessage.Builder( context )
       .title("Title")
       .message("")
       .build()
       .show()
0
Moises Portillo

Les gens de nos jours devraient vérifier/ Les constructeurs dignes de confiance .

Utiliser cette méthode de création d’objet ressemblera à ceci:

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

Un bon exemple d'utilisation «en action» est le cadre vaadin-on-kotlin , qui utilise des générateurs de type sécurité pour assembler des vues et des composants .

0
danield

Je dirais que le modèle et la mise en œuvre restent à peu près les mêmes à Kotlin. Vous pouvez parfois l'ignorer grâce aux valeurs par défaut, mais pour une création d'objet plus complexe, les générateurs restent un outil utile qui ne peut pas être omis.

0
Ritave
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
Brandon Rude

vous pouvez utiliser le paramètre optionnel dans kotlin exemple:

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

puis 

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

Je travaillais sur un projet Kotlin qui exposait une API consommée par les clients Java (qui ne peut tirer parti des constructions en langage Kotlin). Nous devions ajouter des générateurs pour les rendre utilisables en Java. J'ai donc créé une annotation @Builder: https://github.com/ThinkingLogic/kotlin-builder-annotation - il s'agit en gros du remplacement de l'annotation Lombok @Builder. pour Kotlin.

0
YetAnotherMatt