Padrão Builder
Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 48 de 57.
O padrão builder cria um objeto passo a passo e verifica tudo no final. O builder reúne as partes em métodos encadeáveis que retornam o próprio builder, e build() cria o objeto concluído:
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)
}
}Dentro de main:
val mail = EmailBuilder().to("ada@x.io").to("bo@x.io").subject("Lunch").build()
println(mail)Saída:
Email(to=[ada@x.io, bo@x.io], subject=Lunch)apply encurta cada etapa: ele executa um bloco com o objeto como this e retorna o objeto, então uma etapa se torna uma linha. check(condition) { message } lança IllegalStateException, o equivalente a require para o estado do objeto:
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 de main:
val request = RequestBuilder().apply {
url = "/users"
method = "POST"
header("Accept", "json")
}.build()
println(request)Saída:
POST /users [Accept: json]Uma função que recebe uma lambda com receptor, do tipo Builder.() -> Unit, transforma o builder em uma pequena DSL (linguagem específica de domínio): dentro das chaves, o builder é this, então o chamador escreve apenas a configuração:
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()Dentro de main:
val lunch = menu("Lunch") {
item("soup", 450)
item("salad", 600)
}
println(lunch)Saída:
== Lunch ==
soup 4.50
salad 6.00Os argumentos nomeados e padrão do Kotlin já abrangem casos simples: Pizza(size = 30, cheese = true) nomeia cada escolha no local da chamada. Um builder é útil quando o objeto é montado em várias etapas, reúne listas ou precisa de verificações entre campos antes de poder existir:
data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true) // não precisa de builder
val mail = EmailBuilder() // um builder para montagem passo a passo
.to("ada@x.io")
.to("bo@x.io") // coleta uma lista
.subject("Plan")
.build() // verifica tudo no finalDesafio
FácilEscreva um builder para a classe de dados fornecida Email. EmailBuilder tem etapas encadeáveis: to(address) (pode ser chamado várias vezes e adiciona destinatários), subject(text), body(text) e urgent(). build() retorna um Email ou lança IllegalStateException com no recipients ou no subject (nessa ordem de verificações). Um e-mail urgente recebe [URGENT] antes do assunto.
O código fornecido lê comandos (to ada@x.io, subject Hello, body text, urgent, send), inicia um novo builder após cada linha --- e imprime o e-mail construído, To: ada@x.io, bo@x.io | [URGENT] Hello | text, ou error: no subject.
Seu código deve ficar em EmailBuilder.kt e Email.kt. Main.kt contém o código de entrada/saída fornecido e não pode ser editado.
Experimente você mesmo
fun main() {
// Código de entrada/saída fornecido: mantenha-o como está
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))
}
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Classes e Objetos
Trabalhando com ArquivosDefinindo ClassesConstrutores PrimáriosBlocos init e ValidaçãoConstrutores SecundáriosRecapitulação - Cartão da Biblioteca4Herança
Classes Abertas e SubclassesSobrescrevendo Membrossuper e ConstrutoresClasses AbstratasRevisão - Diretório de Funcionários2Propriedades e Encapsulamento
Acessores PersonalizadosModificadores de VisibilidadeSetters Privadoslazy e lateinitRevisão - Conta Bancária5Interfaces
Declarando InterfacesMétodos PadrãoMúltiplas InterfacesInterfaces como TiposRecapitulação - Métodos de Pagamento8Genéricos
Funções genéricasClasses genéricasRestrições de tipoVariância com in e outRecapitulação - Fila de prioridade11Projeto: Gerenciamento de Biblioteca
Livros e MembrosEmpréstimo de LivrosPratique por conta própria: Playground de Kotlin