Pattern Builder
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 48 di 57.
Il pattern builder crea un oggetto passo dopo passo e verifica il tutto alla fine. Il builder raccoglie i componenti tramite metodi concatenabili che restituiscono il builder stesso, e build() crea l’oggetto completo:
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)
}
}All’interno di main:
val mail = EmailBuilder().to("ada@x.io").to("bo@x.io").subject("Lunch").build()
println(mail)Output:
Email(to=[ada@x.io, bo@x.io], subject=Lunch)apply abbrevia ogni passaggio: esegue un blocco con l'oggetto come this e restituisce l'oggetto, così un passaggio diventa una sola riga. check(condition) { message } genera IllegalStateException, l'equivalente di require per lo stato dell'oggetto:
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
}
}Dentro main:
val request = RequestBuilder().apply {
url = "/users"
method = "POST"
header("Accept", "json")
}.build()
println(request)Output:
POST /users [Accept: json]Una funzione che accetta una lambda con receiver, di tipo Builder.() -> Unit, trasforma il builder in una piccola DSL (linguaggio specifico di dominio): all'interno delle parentesi graffe, il builder è this, quindi chi la chiama scrive solo la configurazione:
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()All'interno di main:
val lunch = menu("Lunch") {
item("soup", 450)
item("salad", 600)
}
println(lunch)Output:
== Lunch ==
soup 4.50
salad 6.00Gli argomenti con nome e quelli predefiniti di Kotlin coprono già i casi semplici: Pizza(size = 30, cheese = true) indica ogni scelta nel punto di chiamata. Un builder è utile quando l'oggetto viene assemblato in più passaggi, raccoglie liste o richiede controlli tra i campi prima di poter esistere:
data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true) // non serve un builder
val mail = EmailBuilder() // un builder per la costruzione passo dopo passo
.to("ada@x.io")
.to("bo@x.io") // raccoglie un elenco
.subject("Plan")
.build() // controlla tutto alla fineSfida
FacileScrivi un builder per la data class fornita Email. EmailBuilder ha passaggi concatenabili: to(address) (può essere chiamato più volte e aggiunge destinatari), subject(text), body(text) e urgent(). build() restituisce un Email oppure genera IllegalStateException con no recipients o no subject (in quest'ordine di controlli). Un'email urgente riceve [URGENT] davanti all'oggetto.
Il codice fornito legge i comandi (to ada@x.io, subject Hello, body text, urgent, send), avvia un nuovo builder dopo ogni riga --- e stampa l'email creata, To: ada@x.io, bo@x.io | [URGENT] Hello | text, oppure error: no subject.
Il tuo codice va in EmailBuilder.kt e Email.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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))
}
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Classi e oggetti
Lavorare con i fileDefinire le classiCostruttori primariBlocchi init e convalidaCostruttori secondariRiepilogo - Tessera della biblioteca4Ereditarietà
Classi aperte e sottoclassiRidefinizione dei membrisuper e costruttoriClassi astratteRiepilogo - Elenco del personale2Proprietà e incapsulamento
Accessor personalizzatiModificatori di visibilitàSetter privatilazy e lateinitRipasso - Conto bancario5Interfacce
Dichiarare le interfacceMetodi predefinitiInterfacce multipleLe interfacce come tipiRiepilogo - Metodi di pagamento8Tipi generici
Funzioni genericheClassi genericheVincoli sui tipiVarianza con in e outRiepilogo - Coda con priorità11Progetto: Gestione della biblioteca
Libri e sociPrendere in prestito i libriEsercitati da solo: Playground Kotlin