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.