Patron Builder
Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 48 sur 57.
Le patron builder crée un objet étape par étape et vérifie l’ensemble à la fin. Le builder rassemble les éléments dans des méthodes chaînables qui renvoient le builder lui-même, et build() crée l’objet final :
data class Email(val to: List<String>, val subject: String)
class EmailBuilder {
private val recipients = mutableListOf<String>()
private var subject = ""
fun to(address: String): EmailBuilder {
recipients.add(address)
return this
}
fun subject(text: String): EmailBuilder {
subject = text
return this
}
fun build(): Email {
check(recipients.isNotEmpty()) { "no recipients" }
return Email(recipients.toList(), subject)
}
}Dans main :
val mail = EmailBuilder().to("ada@x.io").to("bo@x.io").subject("Lunch").build()
println(mail)Sortie :
Email(to=[ada@x.io, bo@x.io], subject=Lunch)apply raccourcit chaque étape : il exécute un bloc avec l’objet comme this et renvoie l’objet, de sorte qu’une étape tient sur une seule ligne. check(condition) { message } lance une IllegalStateException, l’équivalent de require pour l’état de l’objet :
class RequestBuilder {
var url = ""
var method = "GET"
private val headers = mutableListOf<String>()
fun header(name: String, value: String) = apply { headers.add("$name: $value") }
fun build(): String {
check(url.isNotEmpty()) { "no url" }
return "$method $url " + headers
}
}Dans main :
val request = RequestBuilder().apply {
url = "/users"
method = "POST"
header("Accept", "json")
}.build()
println(request)Sortie :
POST /users [Accept: json]Une fonction qui accepte une lambda avec récepteur, de type Builder.() -> Unit, transforme le builder en un petit DSL (langage spécifique à un domaine) : à l’intérieur des accolades, le builder est this, de sorte que l’appelant n’écrit que la configuration :
class MenuBuilder(private val title: String) {
private val items = mutableListOf<String>()
fun item(name: String, cents: Int) {
items.add("$name %d.%02d".format(cents / 100, cents % 100))
}
fun build() = "== $title ==\n" + items.joinToString("\n")
}
fun menu(title: String, block: MenuBuilder.() -> Unit): String = MenuBuilder(title).apply(block).build()Dans main :
val lunch = menu("Lunch") {
item("soup", 450)
item("salad", 600)
}
println(lunch)Sortie :
== Lunch ==
soup 4.50
salad 6.00Les arguments nommés et par défaut de Kotlin couvrent déjà les cas simples : Pizza(size = 30, cheese = true) nomme chaque choix au niveau de l’appel. Un builder devient utile lorsque l’objet est assemblé en plusieurs étapes, collecte des listes ou nécessite des vérifications entre les champs avant de pouvoir exister :
data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true) // pas besoin de builder
val mail = EmailBuilder() // un builder pour un assemblage étape par étape
.to("ada@x.io")
.to("bo@x.io") // collecte une liste
.subject("Plan")
.build() // vérifie tout à la finDéfi
FacileÉcrivez un builder pour la classe de données fournie Email. EmailBuilder comporte les étapes chaînables suivantes : to(address) (peut être appelée plusieurs fois et ajoute des destinataires), subject(text), body(text) et urgent(). build() renvoie un Email ou lève IllegalStateException avec no recipients ou no subject (dans cet ordre de vérification). Un e-mail urgent reçoit [URGENT] devant son sujet.
Le code fourni lit des commandes (to ada@x.io, subject Hello, body text, urgent, send), démarre un nouveau builder après chaque ligne --- et affiche l’e-mail construit, To: ada@x.io, bo@x.io | [URGENT] Hello | text, ou error: no subject.
Votre code va dans EmailBuilder.kt et Email.kt. Main.kt contient le code d’entrée/sortie fourni et ne peut pas être modifié.
Essayez vous-même
fun main() {
// Code d'entrée/sortie fourni : le garder tel quel
val input = generateSequence(::readLine).toList()
var builder = EmailBuilder()
for (cmd in input) {
when {
cmd == "send" -> {
try {
println(builder.build())
} catch (e: IllegalStateException) {
println("error: ${e.message}")
}
}
cmd == "---" -> builder = EmailBuilder()
cmd == "urgent" -> builder.urgent()
cmd.startsWith("to ") -> builder.to(cmd.drop(3))
cmd.startsWith("subject ") -> builder.subject(cmd.drop(8))
else -> builder.body(cmd.drop(5))
}
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesConstructeurs primairesBlocs init et validationConstructeurs secondairesRécapitulatif - Carte de bibliothèque4Héritage
Classes ouvertes et sous-classesRedéfinition des membressuper et constructeursClasses abstraitesRécapitulatif – Annuaire du personnel2Propriétés et encapsulation
Accesseurs personnalisésModificateurs de visibilitéSetters privéslazy et lateinitRécapitulatif - Compte bancaire5Interfaces
Déclarer des interfacesMéthodes par défautInterfaces multiplesInterfaces en tant que typesRécapitulatif - Méthodes de paiement8Génériques
Fonctions génériquesClasses génériquesContraintes de typeVariance avec in et outRécapitulatif - File de priorité11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Kotlin