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
}
}
}
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 object
s 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" }
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 }
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).
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.
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()
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
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:
Voici un exemple:
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()
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 .
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.
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)
}
}
}
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")
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.