Sobes.tech
Middle

Que padrões de design podem ser usados na linguagem Kotlin?

sobes.tech IA

Resposta da IA

No Kotlin, frequentemente aplicam-se os seguintes padrões:

  • Singleton: Implementa-se facilmente através de object.
    object MeuSingleton {
        fun fazerAlgo() {
            // ...
        }
    }
    
  • Factory Method: Utiliza uma função abstrata na superclasse ou interface, que as subclasses implementam para criar objetos.
    interface Produto {
        fun operacao()
    }
    
    class ProdutoConcretoA : Produto {
        override fun operacao() {
            // ...
        }
    }
    
    abstract class Criador {
        abstract fun metodoFabrica(): Produto
    
        fun algumaOperacao(): Produto {
            val produto = metodoFabrica()
            // ...
            return produto
        }
    }
    
    class CriadorConcretoA : Criador() {
        override fun metodoFabrica(): Produto {
            return ProdutoConcretoA()
        }
    }
    
  • Abstract Factory: Grupo de fábricas para criar famílias relacionadas de objetos.
    interface ProdutoAbstratoA {
        fun funcaoUtilA()
    }
    
    interface ProdutoAbstratoB {
        fun funcaoUtilB()
        fun outraFuncaoUtilB(colaborador: ProdutoAbstratoA)
    }
    
    interface FabricaAbstrata {
        fun criarProdutoA(): ProdutoAbstratoA
        fun criarProdutoB(): ProdutoAbstratoB
    }
    
    // Implementações concretas de produtos e fábricas...
    
  • Builder: Facilita a criação de objetos complexos passo a passo.
    class Pizza private constructor(
        val crosta: String,
        val molho: String?,
        val ingredientes: List<String>
    ) {
        data class Builder(
            var crosta: String = "fina",
            var molho: String? = null,
            val ingredientes: MutableList<String> = mutableListOf()
        ) {
            fun comCrosta(crosta: String) = apply { this.crosta = crosta }
            fun comMolho(molho: String) = apply { this.molho = molho }
            fun adicionarIngrediente(ingrediente: String) = apply { this.ingredientes.add(ingrediente) }
            fun construir() = Pizza(crosta, molho, ingredientes)
        }
    }
    
    fun main() {
        val pizza = Pizza.Builder()
            .comCrosta("grossa")
            .comMolho("tomate")
            .adicionarIngrediente("pepperoni")
            .adicionarIngrediente("cogumelos")
            .construir()
        // ... usar pizza
    }
    
  • Adapter: Permite que objetos com interfaces incompatíveis trabalhem juntos.
    open class Adaptado {
        fun operacaoEspecifica() {
            // ...
        }
    }
    
    interface Alvo {
        fun operacao()
    }
    
    class Adaptador(private val adaptado: Adaptado) : Alvo {
        override fun operacao() {
            adaptado.operacaoEspecifica()
        }
    }
    
  • Decorator: Adiciona novas funções aos objetos existentes sem alterar sua estrutura. Extensões e delegação em Kotlin facilitam muito isso.
    interface FonteDeDados {
        fun lerDados(): String
        fun escreverDados(dados: String)
    }
    
    class FonteDeDadosArquivo(private val nomeArquivo: String) : FonteDeDados {
        override fun lerDados(): String {
            // Leitura do arquivo
            return ""
        }
    
        override fun escreverDados(dados: String) {
            // Escrita no arquivo
        }
    }
    
    class DecoradorEncriptacao(private val fonte: FonteDeDados) : FonteDeDados by fonte {
        override fun lerDados(): String {
            // Descriptografar dados
            val dados = fonte.lerDados()
            return dados // dados descriptografados
        }
    
        override fun escreverDados(dados: String) {
            // Criptografar dados
            fonte.escreverDados(dados) // dados criptografados
        }
    }
    
  • Observer: Define a dependência "um para muitos". Coroutines Flow e propriedades delegadas do Kotlin podem ser usados para implementação.
    // Implementação simples sem Flow
    interface Observador {
        fun atualizar(mensagem: String)
    }
    
    class Assunto {
        private val observadores = mutableListOf<Observador>()
    
        fun anexar(observador: Observador) {
            observadores.add(observador)
        }
    
        fun desanexar(observador: Observador) {
            observadores.remove(observador)
        }
    
        fun notificarObservadores(mensagem: String) {
            observadores.forEach { it.atualizar(mensagem) }
        }
    }
    
  • Strategy: Define uma família de algoritmos, encapsula cada um e os torna intercambiáveis.
    interface EstrategiaDePagamento {
        fun pagar(valor: Double)
    }
    
    class PagarPorCartaoCredito(private val numeroCartao: String) : EstrategiaDePagamento {
        override fun pagar(valor: Double) {
            println("Pagando $valor usando Cartão de Crédito $numeroCartao")
        }
    }
    
    class PagarPorPayPal(private val email: String) : EstrategiaDePagamento {
        override fun pagar(valor: Double) {
            println("Pagando $valor usando PayPal $email")
        }
    }
    
    class Pedido(private val estrategia: EstrategiaDePagamento) {
        fun processarPedido(valor: Double) {
            // ... processamento do pedido
            estrategia.pagar(valor)
        }
    }
    
  • State: Permite que um objeto mude seu comportamento dependendo do estado interno. Classes seladas e expressões when simplificam sua implementação.
    sealed class Estado {
        object Idle : Estado()
        object Carregando : Estado()
        data class Erro(val mensagem: String) : Estado()
        data class Sucesso(val dados: Any) : Estado()
    }
    
    class Contexto {
        var estado: Estado = Estado.Idle
    
        fun lidarComEvento() {
            when (estado) {
                Estado.Idle -> {
                    println("Lidando com evento no estado Idle")
                    estado = Estado.Carregando
                }
                Estado.Carregando -> {
                    println("Lidando com evento no estado Carregando, ignorando")
                }
                is Estado.Erro -> {
                    println("Lidando com evento no estado Erro: ${estado.mensagem}")
                    // Possível transição para outro estado
                }
                is Estado.Sucesso -> {
                    println("Lidando com evento no estado Sucesso: ${estado.dados}")
                    // Possível transição para outro estado
                }
            }
        }
    }
    
  • Repository: Abstrai a lógica de acesso a dados da lógica de negócio.

Muitos outros padrões, como Facade, Proxy, Command, Iterator, Template Method e Visitor, também são aplicáveis em Kotlin. As características do Kotlin, como extensões, delegação, data classes e classes seladas, frequentemente tornam a implementação de padrões mais idiomática e concisa.