Builder-Muster
Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 48 von 57.
Das Builder-Muster erstellt ein Objekt Schritt für Schritt und überprüft das gesamte Objekt am Ende. Der Builder sammelt die Bestandteile in verkettbaren Methoden, die den Builder selbst zurückgeben, und build() erstellt das fertige Objekt:
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)
}
}Innerhalb von main:
val mail = EmailBuilder().to("ada@x.io").to("bo@x.io").subject("Lunch").build()
println(mail)Ausgabe:
Email(to=[ada@x.io, bo@x.io], subject=Lunch)apply verkürzt jeden Schritt: Es führt einen Block mit dem Objekt als this aus und gibt das Objekt zurück, sodass ein Schritt zu einer Zeile wird. check(condition) { message } löst IllegalStateException aus, das Gegenstück zu require für den Zustand des Objekts:
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
}
}Innerhalb von main:
val request = RequestBuilder().apply {
url = "/users"
method = "POST"
header("Accept", "json")
}.build()
println(request)Ausgabe:
POST /users [Accept: json]Eine Funktion, die ein Lambda mit Empfänger vom Typ Builder.() -> Unit entgegennimmt, verwandelt den Builder in eine kleine DSL (domänenspezifische Sprache): Innerhalb der geschweiften Klammern ist der Builder this, sodass der Aufrufer nur die Konfiguration schreibt:
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()Innerhalb von main:
val lunch = menu("Lunch") {
item("soup", 450)
item("salad", 600)
}
println(lunch)Ausgabe:
== Lunch ==
soup 4.50
salad 6.00Kotlins benannte und Standardargumente decken bereits einfache Fälle ab: Pizza(size = 30, cheese = true) benennt jede Auswahl am Aufrufort. Ein Builder lohnt sich, wenn das Objekt über mehrere Schritte zusammengesetzt wird, Listen sammelt oder vor seiner Erstellung Prüfungen über mehrere Felder hinweg benötigt:
data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true) // kein Builder nötig
val mail = EmailBuilder() // ein Builder für schrittweisen Aufbau
.to("ada@x.io")
.to("bo@x.io") // sammelt eine Liste
.subject("Plan")
.build() // prüft alles am EndeAufgabe
EinfachSchreibe einen Builder für die bereitgestellte Datenklasse Email. EmailBuilder verfügt über verkettbare Schritte: to(address) (kann mehrmals aufgerufen werden und fügt Empfänger hinzu), subject(text), body(text) und urgent(). build() gibt ein Email zurück oder löst IllegalStateException mit no recipients oder no subject aus (in dieser Reihenfolge der Prüfungen). Eine dringende E-Mail erhält [URGENT] vor ihrem Betreff.
Der bereitgestellte Code liest Befehle (to ada@x.io, subject Hello, body text, urgent, send), startet nach jeder Zeile mit --- einen neuen Builder und gibt die erstellte E-Mail, To: ada@x.io, bo@x.io | [URGENT] Hello | text, oder error: no subject aus.
Dein Code gehört in EmailBuilder.kt und Email.kt. Main.kt enthält den bereitgestellten Ein-/Ausgabecode und kann nicht bearbeitet werden.
Probier es selbst
fun main() {
// Bereitgestellter Ein-/Ausgabecode: belasse ihn unverändert
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))
}
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenPrimärkonstruktoreninit-Blöcke und ValidierungSekundärkonstruktorenRückblick – Bibliotheksausweis4Vererbung
Offene Klassen und UnterklassenÜberschreiben von Membernsuper und KonstruktorenAbstrakte KlassenRückblick – Mitarbeiterverzeichnis2Eigenschaften und Kapselung
Benutzerdefinierte ZugriffsmethodenSichtbarkeitsmodifikatorenPrivate Setterlazy und lateinitWiederholung – Bankkonto5Schnittstellen
Schnittstellen deklarierenStandardmethodenMehrere SchnittstellenSchnittstellen als TypenRückblick – Zahlungsmethoden8Generics
Generische FunktionenGenerische KlassenTypbeschränkungenVarianz mit in und outRückblick – Prioritätswarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Kotlin-Playground