Kotlin: Delegação e Delegação de Propriedades do Kotlin…

Última atualização: 2026-08-26

Delegação é o recurso matador da linguagem Kotlin — uma linha para inicialização lazy thread-safe, uma linha para monitoramento de mudança de propriedade. Charlie usa delegação de classes para substituir o boilerplate do padrão Decorator, e delegação de propriedades para auto-persistir propriedades de configuração do OrderProcessor.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Ponto de Dor: Boilerplate do Padrão Decorator

Charlie usou o padrão Decorator do Java para adicionar logging e cache ao OrderRepository. Cada camada exigia 20+ linhas de métodos de delegação — a interface tem 8 métodos, e 3 camadas de decorator significavam 480 linhas de código de delegação pura.

(2) Solução com Delegação de Classes do Kotlin

KOTLIN
// Java: 20+ linhas por método por decorator
class LoggedRepository implements OrderRepository {
    private final OrderRepository delegate;
    @Override public Order findById(String id) { log(); return delegate.findById(id); }
    // ... repetir para todos os 8 métodos
}

// Kotlin: 1 linha delega TODOS os métodos
class LoggedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate  // Auto-delega todos os métodos!

Uma linha substitui 20+ linhas de delegação de métodos — apenas sobrescreva os métodos que deseja melhorar.


3. Delegação de Classes (by)

(1) Sintaxe Básica

KOTLIN
interface OrderRepository {
    fun findById(id: String): Order?
    fun save(order: Order)
    fun findAll(): List<Order>
}

class InMemoryRepository : OrderRepository {
    private val storage = mutableMapOf<String, Order>()
    override fun findById(id: String) = storage[id]
    override fun save(order: Order) { storage[order.id] = order }
    override fun findAll() = storage.values.toList()
}

// Delegação de classes: auto-delega todos os métodos da interface
class LoggedRepository(
    private val inner: OrderRepository
) : OrderRepository by inner {  // Todos os métodos delegados!
    // Sobrescrever apenas o que deseja melhorar
    override fun save(order: Order) {
        println("Salvando pedido: ${order.id}")
        inner.save(order)  // Chamar delegate explicitamente
    }
}

(2) Delegação Multi-Camada

KOTLIN
// Camada 1: Cache
class CachedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate {
    private val cache = mutableMapOf<String, Order>()

    override fun findById(id: String): Order? {
        return cache.getOrPut(id) { delegate.findById(id)!! }
    }
}

// Camada 2: Logging
class LoggedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate {
    override fun save(order: Order) {
        println("Salvando: ${order.id}")
        delegate.save(order)
    }
}

// Compor: logging + cache + em memória
val repo = LoggedRepository(CachedRepository(InMemoryRepository()))

(3) Delegação de Classes vs Herança vs Decorator

Dimensão Herança Decorator (Java) Delegação de Classes (Kotlin)
Volume de código Baixo Alto (boilerplate) Baixo (by)
Flexibilidade Herança única Composição multi-camada Composição multi-camada
Acoplamento Alto Baixo Baixo
Tempo de execução Ligação à classe pai Ligação ao objeto delegate Ligação ao objeto delegate

4. Delegação de Propriedades: by lazy

(1) Uso Básico

KOTLIN
class OrderProcessor {
    // Lazy: computado no primeiro acesso, thread-safe por padrão
    val cache: OrderCache by lazy {
        println("Inicializando cache...")
        OrderCache(maxSize = 10_000)
    }

    // Lazy com thread safety personalizada
    val heavyConfig by lazy(LazyThreadSafetyMode.PUBLICATION) {
        loadConfig()  // Pode ser chamado múltiplas vezes, mas apenas o primeiro resultado é usado
    }
}

(2) Modos de Thread Safety do lazy

Modo Comportamento Desempenho Caso de Uso
SYNCHRONIZED (padrão) Init única, double-checked locking Ligeiramente mais lento Ambientes multi-thread
PUBLICATION Múltiplas threads podem computar, apenas primeiro resultado usado Mais rápido Init sem efeitos colaterais
NONE Sem sincronização Mais rápido Contexto single-thread conhecido

5. Delegação de Propriedades: observable / vetoable

(1) observable — Notificação de Mudança

KOTLIN
import kotlin.properties.Delegates

class Order(var id: String) {
    // observable: notificar em cada mudança
    var status: String by Delegates.observable("PENDING") { _, old, new ->
        println("Status alterado: $old -> $new")
    }
}

val order = Order("ORD-001")
order.status = "CONFIRMED"  // Imprime: Status alterado: PENDING -> CONFIRMED
order.status = "SHIPPED"    // Imprime: Status alterado: CONFIRMED -> SHIPPED

(2) vetoable — Interceptação de Mudança

KOTLIN
class Order(var id: String) {
    // vetoable: pode rejeitar a mudança
    var total: Double by Delegates.vetoable(0.0) { _, old, new ->
        if (new < 0) {
            println("Rejeitando total negativo: $new")
            false  // Veto: manter valor antigo
        } else {
            true  // Aceitar a mudança
        }
    }
}

val order = Order("ORD-001")
order.total = 299.99  // Aceito
order.total = -50.0   // Rejeitado: imprime aviso, mantém 299.99

(3) Comparação observable vs vetoable

Delegate Comportamento Valor de Retorno Caso de Uso
observable Notificar mudança, não interceptar Unit Logging / auditoria
vetoable Pode interceptar e rejeitar mudança Boolean Validação / restrições

6. Delegação de Propriedade Personalizada

(1) Interface ReadWriteProperty

KOTLIN
import kotlin.reflect.KProperty

class PersistentProperty<T>(
    private val key: String,
    private val defaultValue: T
) : ReadWriteProperty<Any?, T> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        // Carregar da persistência (simplificado)
        return loadFromStore(key) ?: defaultValue
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        // Salvar na persistência
        saveToStore(key, value)
    }
}

// Persistência simulada
private val store = mutableMapOf<String, Any?>()
private fun <T> loadFromStore(key: String): T? = store[key] as T?
private fun <T> saveToStore(key: String, value: T) { store[key] = value }

(2) Usando Delegates Personalizados

KOTLIN
class OrderConfig {
    var maxRetries: Int by PersistentProperty("order.max_retries", 3)
    var timeout: Long by PersistentProperty("order.timeout", 5_000L)
    var taxRate: Double by PersistentProperty("order.tax_rate", 0.08)
}

val config = OrderConfig()
println(config.maxRetries)  // 3 (padrão)
config.maxRetries = 5
println(config.maxRetries)  // 5 (persistido)

(3) Diagrama de Classes de Delegates

100%
classDiagram
    class ReadOnlyProperty {
        <<interface>>
        +getValue(thisRef, property): T
    }
    class ReadWriteProperty {
        <<interface>>
        +getValue(thisRef, property): T
        +setValue(thisRef, property, value: T)
    }
    class LazyDelegate {
        +getValue(): T
    }
    class ObservableDelegate {
        +setValue()
    }
    class VetoableDelegate {
        +setValue()
    }
    class CustomDelegate {
        +getValue()
        +setValue()
    }
    ReadOnlyProperty <|-- ReadWriteProperty
    ReadOnlyProperty <|-- LazyDelegate
    ReadWriteProperty <|-- ObservableDelegate
    ReadWriteProperty <|-- VetoableDelegate
    ReadWriteProperty <|-- CustomDelegate

7. Exemplo Completo: OrderProcessor Arquitetura de Delegação

KOTLIN
// ============================================
// OrderProcessor - Arquitetura de Delegação
// Recurso: Delegação de classes + delegação de propriedades
// ============================================

import kotlin.properties.Delegates
import kotlin.reflect.KProperty

// --- Delegação de Classes ---

interface OrderRepository {
    fun findById(id: String): Order?
    fun save(order: Order)
    fun findAll(): List<Order>
}

data class Order(val id: String, var total: Double, var status: String, val customer: String)

class InMemoryRepository : OrderRepository {
    private val storage = mutableMapOf<String, Order>()
    override fun findById(id: String) = storage[id]
    override fun save(order: Order) { storage[order.id] = order }
    override fun findAll() = storage.values.toList()
}

class LoggedRepository(private val inner: OrderRepository) : OrderRepository by inner {
    override fun save(order: Order) {
        println("  [LOG] Salvando pedido: ${order.id} (\$${order.total} USD)")
        inner.save(order)
    }
}

class ValidatedRepository(private val inner: OrderRepository) : OrderRepository by inner {
    override fun save(order: Order) {
        require(order.total >= 0) { "Total deve ser não-negativo" }
        require(order.id.startsWith("ORD-")) { "Formato de ID de pedido inválido" }
        inner.save(order)
    }
}

// --- Delegação de Propriedades ---

private val configStore = mutableMapOf<String, Any?>()

class ConfigProperty<T>(private val key: String, private val default: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        @Suppress("UNCHECKED_CAST")
        return configStore[key] as? T ?: default
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        configStore[key] = value
        println("  [CONFIG] $key = $value")
    }
}

class OrderProcessorConfig {
    var maxOrderValue: Double by ConfigProperty("max_order_value", 100_000.0)
    var defaultTaxRate: Double by ConfigProperty("default_tax_rate", 0.08)
    var enableAuditing: Boolean by Delegates.observable(true) { _, _, new ->
        println("  [CONFIG] Auditoria ${if (new) "habilitada" else "desabilitada"}")
    }
    var processingTimeout: Long by Delegates.vetoable(30_000L) { _, _, new ->
        if (new < 1_000) { println("  [CONFIG] Rejeitando timeout < 1000ms"); false }
        else true
    }
}

fun main() {
    // Compor: validação + logging + em memória
    val repo: OrderRepository = ValidatedRepository(LoggedRepository(InMemoryRepository()))

    println("=== Salvando Pedidos ===")
    repo.save(Order("ORD-001", 299.99, "CONFIRMED", "Alice"))
    repo.save(Order("ORD-002", 15_000.00, "PENDING", "Bob"))

    println("\n=== Encontrar Pedido ===")
    println(repo.findById("ORD-001"))

    // Delegação de propriedades
    println("\n=== Configuração ===")
    val config = OrderProcessorConfig()
    println("Valor máximo do pedido: \$${config.maxOrderValue} USD")
    config.defaultTaxRate = 0.10
    config.enableAuditing = false
    config.processingTimeout = 500  // Rejeitado
    config.processingTimeout = 60_000  // Aceito
    println("Timeout: ${config.processingTimeout}ms")
}

Saída:

TEXT 📖 Somente leitura
=== Salvando Pedidos ===
  [LOG] Salvando pedido: ORD-001 ($299.99 USD)
  [LOG] Salvando pedido: ORD-002 ($15000.0 USD)

=== Encontrar Pedido ===
Order(id=ORD-001, total=299.99, status=CONFIRMED, customer=Alice)

=== Configuração ===
Valor máximo do pedido: $100000.0 USD
  [CONFIG] default_tax_rate = 0.1
  [CONFIG] Auditoria desabilitada
  [CONFIG] Rejeitando timeout < 1000ms
  [CONFIG] processingTimeout = 60000
Timeout: 60000ms

9. Exemplos práticos rápidos

▶ Exemplo: Delegação de interface

KOTLIN
interface Renderer {
    fun render(content: String)
}

class HtmlRenderer : Renderer {
    override fun render(content: String) = println("<div>$content</div>")
}

class MarkdownRenderer : Renderer {
    override fun render(content: String) = println("**$content**")
}

// LoggerRenderer delega render() para o renderer subjacente
class LoggerRenderer(private val delegate: Renderer) : Renderer by delegate {
    override fun render(content: String) {
        println("[LOG] rendering...")
        delegate.render(content)
        println("[LOG] done")
    }
}

HtmlRenderer().let { LoggerRenderer(it).render("Hello") }
MarkdownRenderer().let { LoggerRenderer(it).render("World") }

// Saída:
// [LOG] rendering...
// <div>Hello</div>
// [LOG] done
// [LOG] rendering...
// **World**
// [LOG] done

▶ Exemplo: Property delegation - lazy

KOTLIN
class ExpensiveResource {
    val data: String by lazy {
        println("Carregando recurso caro...")
        Thread.sleep(1000)
        "Data loaded"
    }
}

val resource = ExpensiveResource()
println("Antes do acesso")
println("Acessando: ${resource.data}")  // carregado aqui
println("Novamente: ${resource.data}")   // reutilizado

// Saída:
// Antes do acesso
// Carregando recurso caro...
// Acessando: Data loaded
// Novamente: Data loaded

▶ Exemplo: Property delegation - observable

KOTLIN
import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("Inicial") { _, old, new ->
        println("Nome alterado: $old -> $new")
    }

    var age: Int by Delegates.vetoable(0) { _, _, new ->
        new >= 0  // permite apenas idade não-negativa
    }
}

val user = User()
user.name = "Alice"
user.name = "Bob"
println("Nome atual: ${user.name}")

user.age = -5   // rejeitado
println("Idade: ${user.age}")
user.age = 30    // aceito
println("Idade: ${user.age}")

// Saída:
// Nome alterado: Inicial -> Alice
// Nome alterado: Alice -> Bob
// Nome atual: Bob
// Idade: 0
// Idade: 30

▶ Exemplo: Map delegation

KOTLIN
// Map como delegação - data classes dinâmicas
class Cart(val items: Map<String, Any>) {
    val total: Double by items              // mapeia para items["total"]
    val customerId: String by items         // mapeia para items["customerId"]
    val quantity: Int by items              // mapeia para items["quantity"]
}

val data = mapOf(
    "total" to 299.99,
    "customerId" to "Alice",
    "quantity" to 3
)

val cart = Cart(data)
println("Total: \$${cart.total}")
println("Cliente: ${cart.customerId}")
println("Quantidade: ${cart.quantity}")

// Saída:
// Total: $299.99
// Cliente: Alice
// Quantidade: 3

▶ Exemplo: Custom property delegate

KOTLIN
// Implementação de getValue/setValue para delegate próprio
import kotlin.reflect.KProperty

class RangeDelegate(private val range: IntRange) {
    private var value: Int = range.first

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
        value = newValue.coerceIn(range)
    }
}

class Settings {
    var volume: Int by RangeDelegate(0..100)
    var priority: Int by RangeDelegate(1..5)
}

val settings = Settings()
settings.volume = 150
println("Volume: ${settings.volume}")  // 100 (limite)
settings.volume = -10
println("Volume: ${settings.volume}")  // 0 (limite)
settings.volume = 75
println("Volume: ${settings.volume}")  // 75

settings.priority = 10
println("Prioridade: ${settings.priority}")  // 5 (limite)
settings.priority = 3
println("Prioridade: ${settings.priority}")  // 3

// Saída:
// Volume: 100
// Volume: 0
// Volume: 75
// Prioridade: 5
// Prioridade: 3

▶ Exemplo: delegatedTo (apenas get)

KOTLIN
// Delegate de propriedade somente leitura - usa stringDelegate
class FormattedValue(private val formatter: (Any) -> String) {
    private var value: Any? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return if (value != null) formatter(value!!) else "(vazio)"
    }

    fun set(value: Any) {
        this.value = value
    }
}

class Profile {
    private val name = FormattedValue { it.toString().uppercase() }
    private val price = FormattedValue { "\$$it USD" }

    val nameView: String get() = name.getValue(null, ::nameView)
    val priceView: String get() = price.getValue(null, ::priceView)

    fun update(name: String, price: Double) {
        this.name.set(name)
        this.price.set(price)
    }
}

val profile = Profile()
println(profile.nameView)  // (vazio)
profile.update("Alice", 299.99)
println("Nome: ${profile.nameView}")
println("Preço: ${profile.priceView}")

▶ Exemplo: notNull delegate

KOTLIN
// notNull: inicialização tardia sem lateinit, funciona com qualquer tipo
import kotlin.properties.Delegates

class Configuration {
    var apiKey: String by Delegates.notNull()
    var maxRetries: Int by Delegates.notNull()
}

val config = Configuration()

// Não foi inicializado ainda - throw no acesso
try {
    println(config.apiKey)
} catch (e: IllegalStateException) {
    println("Não inicializado: ${e.message}")
}

config.apiKey = "sk-prod-XXX"
config.maxRetries = 5
println("ApiKey: ${config.apiKey}")
println("MaxRetries: ${config.maxRetries}")

❓ Perguntas Frequentes

P: Qual a diferença entre delegação de classes e herança? R: Delegação de classes é composição (tem-um); herança é subtipagem (é-um). Delegação é mais flexível — você pode substituir o delegate em tempo de execução e evitar o acoplamento forte da herança.

P: A inicialização by lazy sempre acontece? R: Não necessariamente. Se a propriedade nunca é acessada, a inicialização nunca executa. Esta é a principal vantagem do lazy — evitar sobrecarga de inicialização desnecessária.

P: Qual o desempenho da delegação de propriedades? R: Cada leitura/escrita de propriedade incorre em uma chamada de função extra (getValue/setValue), mas a otimização de inlining da JVM tipicamente elimina essa sobrecarga. Para caminhos quentes, meça antes de decidir.

P: Uma classe pode usar múltiplas delegações by? R: Delegação de classes delega uma interface (múltipla herança via múltiplas interfaces), e delegação de propriedades delega cada propriedade independentemente.

P: observable pode ser usado para programação reativa? R: Seu escopo é limitado — notifica apenas dentro do mesmo objeto. Para reatividade entre componentes, use StateFlow/SharedFlow. observable é mais adequado para logs de auditoria e notificações simples.

P: Delegates personalizados devem implementar ReadWriteProperty? R: Não é necessário. Você só precisa fornecer getValue (e setValue para var). ReadWriteProperty é apenas uma interface de conveniência.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Use by lazy para implementar uma conexão de banco de dados com inicialização lazy que imprime "Conectando..." no primeiro acesso. Dica: val connection by lazy { ... }
  2. Intermediário (⭐⭐): Use delegação de classes para implementar LoggedOrderRepo, sobrescrevendo apenas o método save para adicionar logging. Dica: class LoggedOrderRepo(val inner: OrderRepository) : OrderRepository by inner { ... }
  3. Desafio (⭐⭐⭐): Implemente um delegate de propriedade personalizado TraceDelegate que imprime o nome e valor da propriedade em cada leitura e escrita. Dica: funções operadoras getValue + setValue

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%