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á
- Delegação de classes: padrão Decorator com zero boilerplate
by lazy: computação lazy thread-safeDelegates.observable/Delegates.vetoable: monitoramento e interceptação de mudança de propriedade- Delegação de propriedade personalizada:
ReadWriteProperty/getValue/setValue - Charlie em ação:
ValidatedRepository+ConfigPropertydelegação personalizada
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
// 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
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
// 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
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
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
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
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
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
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
// ============================================
// 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:
=== 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
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
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
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
// 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
// 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)
// 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
// 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 lazysempre acontece? R: Não necessariamente. Se a propriedade nunca é acessada, a inicialização nunca executa. Esta é a principal vantagem dolazy— 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:
observablepode 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 fornecergetValue(esetValueparavar).ReadWritePropertyé apenas uma interface de conveniência.
📖 Resumo
- Delegação de classes com
byauto-delega todos os métodos da interface — zero boilerplate do padrão Decorator by lazyfornece inicialização lazy thread-safe, ideal para criação sob demanda de objetos pesadosDelegates.observablemonitora mudanças de propriedade;Delegates.vetoablepode interceptá-las e rejeitá-las- Delegates de propriedade personalizados só precisam de funções operadoras
getValue/setValue - Prefira delegação a herança — composição é mais flexível que subclasses
- Composição de delegação multi-camada alcança separação de preocupações: logging + cache + validação
📝 Exercícios
- Iniciante (⭐): Use
by lazypara implementar uma conexão de banco de dados com inicialização lazy que imprime "Conectando..." no primeiro acesso. Dica:val connection by lazy { ... } - Intermediário (⭐⭐): Use delegação de classes para implementar
LoggedOrderRepo, sobrescrevendo apenas o métodosavepara adicionar logging. Dica:class LoggedOrderRepo(val inner: OrderRepository) : OrderRepository by inner { ... } - Desafio (⭐⭐⭐): Implemente um delegate de propriedade personalizado
TraceDelegateque imprime o nome e valor da propriedade em cada leitura e escrita. Dica: funções operadorasgetValue+setValue