Design patterns sao solucoes recorrentes para problemas comuns no desenvolvimento de software. Kotlin traz recursos da linguagem que tornam muitos desses padroes mais concisos e expressivos do que em Java. Neste artigo, vamos implementar os padroes mais importantes usando recursos idiomaticos de Kotlin.
Singleton
Em Java, implementar Singleton exige cuidado com threads e lazy initialization. Em Kotlin, a palavra-chave object resolve tudo em uma linha:
object BancoDeDados {
private val conexoes = mutableListOf<String>()
fun conectar(url: String) {
conexoes.add(url)
println("Conectado a $url. Total: ${conexoes.size}")
}
fun desconectarTodas() {
conexoes.clear()
println("Todas as conexoes encerradas")
}
}
fun main() {
BancoDeDados.conectar("jdbc:postgresql://localhost/app")
BancoDeDados.conectar("jdbc:postgresql://localhost/cache")
BancoDeDados.desconectarTodas()
}
O compilador Kotlin garante que a instancia e criada de forma thread-safe e lazy. Nao e necessario double-checked locking nem enum tricks.
Factory Method
O Factory Method encapsula a criacao de objetos, permitindo que subclasses decidam qual tipo instanciar. Em Kotlin, companion objects com funcoes factory sao a abordagem idiomatica:
sealed class Notificacao {
abstract fun enviar(destinatario: String, mensagem: String)
class EmailNotificacao(private val servidorSmtp: String) : Notificacao() {
override fun enviar(destinatario: String, mensagem: String) {
println("Email para $destinatario via $servidorSmtp: $mensagem")
}
}
class SmsNotificacao(private val provedor: String) : Notificacao() {
override fun enviar(destinatario: String, mensagem: String) {
println("SMS para $destinatario via $provedor: $mensagem")
}
}
class PushNotificacao(private val plataforma: String) : Notificacao() {
override fun enviar(destinatario: String, mensagem: String) {
println("Push para $destinatario na $plataforma: $mensagem")
}
}
companion object {
fun criar(tipo: String): Notificacao = when (tipo) {
"email" -> EmailNotificacao("smtp.empresa.com")
"sms" -> SmsNotificacao("twilio")
"push" -> PushNotificacao("firebase")
else -> throw IllegalArgumentException("Tipo desconhecido: $tipo")
}
}
}
fun main() {
val notificacao = Notificacao.criar("email")
notificacao.enviar("ana@email.com", "Bem-vinda ao sistema")
val sms = Notificacao.criar("sms")
sms.enviar("+5511999887766", "Seu codigo: 1234")
}
A combinacao de sealed class com companion object factory cria um padrao robusto onde o compilador garante que todos os tipos sao tratados em expressoes when.
Builder
O pattern Builder simplifica a criacao de objetos com muitos parametros. Em Kotlin, parametros nomeados e valores padrao frequentemente eliminam a necessidade de um Builder explicito:
// Abordagem idiomatica: parametros nomeados com valores padrao
data class ServidorConfig(
val host: String = "localhost",
val porta: Int = 8080,
val maxConexoes: Int = 100,
val timeout: Long = 30_000,
val ssl: Boolean = false,
val logLevel: String = "INFO"
)
fun main() {
val config = ServidorConfig(
host = "api.empresa.com",
porta = 443,
ssl = true,
logLevel = "DEBUG"
)
println(config)
}
Quando a construcao envolve logica mais complexa, um DSL Builder e mais apropriado:
class QueryBuilder {
private var tabela: String = ""
private val colunas = mutableListOf<String>()
private val condicoes = mutableListOf<String>()
private var ordenacao: String? = null
private var limite: Int? = null
fun de(tabela: String) = apply { this.tabela = tabela }
fun colunas(vararg cols: String) = apply { colunas.addAll(cols) }
fun onde(condicao: String) = apply { condicoes.add(condicao) }
fun ordenarPor(coluna: String) = apply { ordenacao = coluna }
fun limitar(n: Int) = apply { limite = n }
fun build(): String {
require(tabela.isNotBlank()) { "Tabela e obrigatoria" }
val cols = if (colunas.isEmpty()) "*" else colunas.joinToString(", ")
val sql = StringBuilder("SELECT $cols FROM $tabela")
if (condicoes.isNotEmpty()) {
sql.append(" WHERE ${condicoes.joinToString(" AND ")}")
}
ordenacao?.let { sql.append(" ORDER BY $it") }
limite?.let { sql.append(" LIMIT $it") }
return sql.toString()
}
}
fun query(bloco: QueryBuilder.() -> Unit): String {
return QueryBuilder().apply(bloco).build()
}
fun main() {
val sql = query {
de("usuarios")
colunas("nome", "email", "idade")
onde("idade > 18")
onde("ativo = true")
ordenarPor("nome")
limitar(50)
}
println(sql)
// SELECT nome, email, idade FROM usuarios WHERE idade > 18 AND ativo = true ORDER BY nome LIMIT 50
}
Observer
O pattern Observer define uma dependencia um-para-muitos entre objetos. Em Kotlin, StateFlow e SharedFlow do kotlinx.coroutines sao a implementacao moderna desse padrao:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class LojaDeEstado<T>(valorInicial: T) {
private val _estado = MutableStateFlow(valorInicial)
val estado: StateFlow<T> = _estado.asStateFlow()
fun atualizar(transformacao: (T) -> T) {
_estado.value = transformacao(_estado.value)
}
}
data class AppState(
val usuario: String = "",
val logado: Boolean = false,
val itensCarrinho: Int = 0
)
fun main() = runBlocking {
val loja = LojaDeEstado(AppState())
// Observador 1: UI
val jobUI = launch {
loja.estado.collect { estado ->
println("[UI] Usuario: ${estado.usuario}, Carrinho: ${estado.itensCarrinho}")
}
}
// Observador 2: Analytics
val jobAnalytics = launch {
loja.estado
.filter { it.logado }
.collect { estado ->
println("[Analytics] Evento: usuario ${estado.usuario} ativo")
}
}
delay(100)
loja.atualizar { it.copy(usuario = "Ana", logado = true) }
delay(100)
loja.atualizar { it.copy(itensCarrinho = 3) }
delay(100)
jobUI.cancel()
jobAnalytics.cancel()
}
Para uma implementacao classica sem coroutines:
interface Observador<T> {
fun atualizar(valor: T)
}
class EventBus<T> {
private val observadores = mutableListOf<Observador<T>>()
fun inscrever(observador: Observador<T>) {
observadores.add(observador)
}
fun cancelar(observador: Observador<T>) {
observadores.remove(observador)
}
fun notificar(valor: T) {
observadores.forEach { it.atualizar(valor) }
}
}
Strategy
O pattern Strategy permite trocar algoritmos em tempo de execucao. Em Kotlin, funcoes de ordem superior tornam esse padrao extremamente conciso:
data class Produto(val nome: String, val preco: Double, val quantidade: Int)
// Estrategias como funcoes de primeira classe
typealias EstrategiaDesconto = (Double) -> Double
val semDesconto: EstrategiaDesconto = { total -> total }
val desconto10Porcento: EstrategiaDesconto = { total -> total * 0.90 }
val desconto20Porcento: EstrategiaDesconto = { total -> total * 0.80 }
val descontoProgressivo: EstrategiaDesconto = { total ->
when {
total > 1000 -> total * 0.85
total > 500 -> total * 0.90
total > 200 -> total * 0.95
else -> total
}
}
class Carrinho {
private val itens = mutableListOf<Produto>()
fun adicionar(produto: Produto) {
itens.add(produto)
}
fun calcularTotal(estrategia: EstrategiaDesconto): Double {
val subtotal = itens.sumOf { it.preco * it.quantidade }
return estrategia(subtotal)
}
}
fun main() {
val carrinho = Carrinho()
carrinho.adicionar(Produto("Notebook", 3500.0, 1))
carrinho.adicionar(Produto("Mouse", 150.0, 2))
println("Sem desconto: R$ ${"%.2f".format(carrinho.calcularTotal(semDesconto))}")
println("10% off: R$ ${"%.2f".format(carrinho.calcularTotal(desconto10Porcento))}")
println("Progressivo: R$ ${"%.2f".format(carrinho.calcularTotal(descontoProgressivo))}")
}
Decorator
O pattern Decorator adiciona comportamento a objetos sem alterar sua classe. Em Kotlin, extension functions e delegacao com by tornam isso elegante:
interface Logger {
fun log(mensagem: String)
}
class ConsoleLogger : Logger {
override fun log(mensagem: String) {
println(mensagem)
}
}
class TimestampLogger(private val inner: Logger) : Logger by inner {
override fun log(mensagem: String) {
val agora = java.time.LocalDateTime.now()
inner.log("[$agora] $mensagem")
}
}
class PrefixLogger(private val inner: Logger, private val prefixo: String) : Logger by inner {
override fun log(mensagem: String) {
inner.log("[$prefixo] $mensagem")
}
}
fun main() {
val logger = PrefixLogger(
TimestampLogger(ConsoleLogger()),
"APP"
)
logger.log("Aplicacao iniciada")
logger.log("Processando requisicao")
// [APP] [2026-03-17T10:30:00] Aplicacao iniciada
}
Adapter
O Adapter converte a interface de uma classe para outra esperada pelo cliente. A delegacao de Kotlin com by simplifica:
// Sistema legado
class SistemaLegado {
fun buscarDadosXml(): String {
return "<usuario><nome>Ana</nome><idade>28</idade></usuario>"
}
}
// Interface moderna esperada
interface FonteDeDados {
fun buscarUsuario(): Map<String, String>
}
// Adapter
class SistemaLegadoAdapter(private val legado: SistemaLegado) : FonteDeDados {
override fun buscarUsuario(): Map<String, String> {
val xml = legado.buscarDadosXml()
// Parsing simplificado para exemplo
val nome = Regex("<nome>(.*?)</nome>").find(xml)?.groupValues?.get(1) ?: ""
val idade = Regex("<idade>(.*?)</idade>").find(xml)?.groupValues?.get(1) ?: ""
return mapOf("nome" to nome, "idade" to idade)
}
}
fun exibirDados(fonte: FonteDeDados) {
val dados = fonte.buscarUsuario()
println("Nome: ${dados["nome"]}, Idade: ${dados["idade"]}")
}
fun main() {
val legado = SistemaLegado()
val adapter = SistemaLegadoAdapter(legado)
exibirDados(adapter) // Nome: Ana, Idade: 28
}
State
O pattern State permite que um objeto altere seu comportamento quando seu estado interno muda. Sealed classes em Kotlin sao perfeitas para isso:
sealed class EstadoPedido {
abstract fun proximo(): EstadoPedido
abstract fun descricao(): String
data object Criado : EstadoPedido() {
override fun proximo() = Pago
override fun descricao() = "Pedido criado, aguardando pagamento"
}
data object Pago : EstadoPedido() {
override fun proximo() = Enviado
override fun descricao() = "Pagamento confirmado, preparando envio"
}
data object Enviado : EstadoPedido() {
override fun proximo() = Entregue
override fun descricao() = "Pedido enviado, em transito"
}
data object Entregue : EstadoPedido() {
override fun proximo() = this
override fun descricao() = "Pedido entregue ao destinatario"
}
}
class Pedido(val id: String) {
var estado: EstadoPedido = EstadoPedido.Criado
private set
fun avancar() {
estado = estado.proximo()
println("Pedido $id: ${estado.descricao()}")
}
}
fun main() {
val pedido = Pedido("PED-001")
pedido.avancar() // Pagamento confirmado
pedido.avancar() // Pedido enviado
pedido.avancar() // Pedido entregue
pedido.avancar() // Continua entregue
}
Conclusao
Kotlin transforma a implementacao de design patterns. Recursos como object declarations, sealed classes, extension functions, funcoes de ordem superior e delegacao com by eliminam o boilerplate que torna esses padroes verbosos em Java. O resultado e um codigo mais expressivo e facil de manter. A chave e conhecer os recursos da linguagem e aplica-los onde cada padrao se encaixa naturalmente. Nao force um padrao quando a linguagem ja oferece uma solucao idiomatica mais simples.