O que é Delegation em Kotlin?
Delegation (delegação) é um padrão de design onde um objeto repassa responsabilidades para outro. Em Kotlin, a delegação é suportada nativamente com a palavra-chave by, tanto para classes quanto para propriedades.
Em vez de herdar de uma classe, você delega a implementação para uma instância. É o famoso princípio “composição sobre herança” que fica muito fácil de aplicar em Kotlin.
Pense na delegação como um gerente que distribui tarefas. O gerente (classe principal) não faz tudo sozinho – ele delega para especialistas (objetos delegados). Quando alguém pede um relatório financeiro, o gerente repassa para o contador. O gerente sabe a quem delegar, mas não precisa saber fazer cada tarefa.
Delegação de classe
interface Repositorio {
fun salvar(dados: String)
fun buscar(): String
}
class RepositorioBanco : Repositorio {
override fun salvar(dados: String) = println("Salvando no banco: $dados")
override fun buscar() = "Dados do banco"
}
class RepositorioComLog(repo: Repositorio) : Repositorio by repo {
override fun salvar(dados: String) {
println("[LOG] Operação de salvar iniciada")
// Delega para o repositório original, mas nao automaticamente neste override
}
// buscar() é delegado automaticamente
}
fun main() {
val repo = RepositorioComLog(RepositorioBanco())
println(repo.buscar()) // Dados do banco
repo.salvar("teste") // [LOG] Operação de salvar iniciada
}
O by repo faz com que todos os métodos da interface sejam delegados automaticamente. Você só sobrescreve o que quiser customizar.
Delegação de propriedade
Kotlin oferece delegates prontos pra uso:
import kotlin.properties.Delegates
class Configuração {
// Inicialização preguiçosa
val conexao: String by lazy {
println("Conectando ao banco...")
"Conexão estabelecida"
}
// Observável — executa bloco quando muda
var tema: String by Delegates.observable("claro") { _, antigo, novo ->
println("Tema mudou de '$antigo' para '$novo'")
}
// Não pode ser lido antes de ser atribuído
var usuario: String by Delegates.notNull()
}
fun main() {
val config = Configuração()
println(config.conexao) // "Conectando..." + "Conexão estabelecida"
println(config.conexao) // "Conexão estabelecida" (já inicializou)
config.tema = "escuro" // Tema mudou de 'claro' para 'escuro'
config.usuario = "Karina"
println(config.usuario) // Karina
}
Criando seu próprio delegate
import kotlin.reflect.KProperty
class FormatadoDelegate {
private var valor = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = valor
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
valor = value.trim().lowercase()
}
}
class Formulario {
var email: String by FormatadoDelegate()
}
fun main() {
val form = Formulario()
form.email = " KARINA@Email.COM "
println(form.email) // karina@email.com
}
Delegation em Kotlin é poderoso pra reduzir boilerplate, adicionar comportamentos transversais e manter o código organizado.
Delegação com múltiplas interfaces
Você pode delegar múltiplas interfaces para diferentes objetos, compondo comportamentos de forma flexível:
interface Notificador {
fun notificar(mensagem: String)
}
interface Auditor {
fun registrar(acao: String)
}
class NotificadorEmail : Notificador {
override fun notificar(mensagem: String) {
println("Email enviado: $mensagem")
}
}
class AuditorLog : Auditor {
override fun registrar(acao: String) {
println("Auditoria: $acao em ${System.currentTimeMillis()}")
}
}
class ServicoCompleto(
notificador: Notificador,
auditor: Auditor
) : Notificador by notificador, Auditor by auditor
fun main() {
val servico = ServicoCompleto(NotificadorEmail(), AuditorLog())
servico.notificar("Pedido confirmado")
servico.registrar("PEDIDO_CRIADO")
}
Delegate para válidação de propriedades
Um caso prático é criar delegates que validam valores automaticamente:
import kotlin.reflect.KProperty
class Validado<T>(
private val validacao: (T) -> Boolean,
private val mensagemErro: String
) {
private var valor: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return valor ?: throw IllegalStateException("${property.name} nao foi inicializado")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (!validacao(value)) {
throw IllegalArgumentException("$mensagemErro: $value")
}
valor = value
}
}
class Cadastro {
var idade: Int by Validado({ it in 0..150 }, "Idade invalida")
var nome: String by Validado({ it.isNotBlank() }, "Nome nao pode ser vazio")
}
fun main() {
val cadastro = Cadastro()
cadastro.nome = "Karina"
cadastro.idade = 28
println("${cadastro.nome}, ${cadastro.idade} anos")
try {
cadastro.idade = -5 // Lança IllegalArgumentException
} catch (e: IllegalArgumentException) {
println(e.message) // Idade invalida: -5
}
}
Delegate com Map para configurações dinâmicas
Kotlin permite usar um Map como delegate, ideal para configurações carregadas de arquivos ou banco de dados:
class AppConfig(propriedades: Map<String, Any?>) {
val nome: String by propriedades
val versao: String by propriedades
val debug: Boolean by propriedades
}
fun main() {
val props = mapOf(
"nome" to "MeuApp",
"versao" to "3.2.1",
"debug" to false
)
val config = AppConfig(props)
println("${config.nome} v${config.versao} (debug=${config.debug})")
// MeuApp v3.2.1 (debug=false)
}
Casos de Uso no Mundo Real
- Decorator pattern: adicionar logging, cache ou métricas a uma implementação existente sem alterar o código original, como no exemplo
RepositorioComLog. - Injeção de dependência simplificada: delegar implementações de interfaces permite trocar comportamentos facilmente em testes.
- Propriedades lazy em Android: usar
by lazypara inicializar views, adapters e outros componentes pesados somente quando necessário. - SharedPreferences com delegates: criar delegates personalizados que leem e gravam valores automaticamente no SharedPreferences do Android.
- Propriedades observáveis para UI reativa: usar
Delegates.observablepara notificar mudanças de estado e atualizar a interface automaticamente.
Boas Práticas
- Prefira delegação sobre herança: quando você precisa reutilizar comportamento, considere delegar para uma instância em vez de criar uma hierarquia de classes.
- Use
by lazypara inicializações caras: propriedades que envolvem leitura de arquivo, conexão de banco ou cálculos pesados devem ser lazy. - Mantenha delegates simples: cada delegate deve ter uma única responsabilidade.
- Use property delegates padrão antes de criar os seus.
lazy,observable,notNullevetoablecobrem a maioria dos casos.
Erros Comuns
- Esquecer de chamar o método original no override: ao sobrescrever um método delegado, o
bynão repassa automaticamente. Você precisa manter uma referência ao objeto delegado se quiser chamar a implementação original. - Usar
by lazycomvar:lazysó funciona comval, pois o valor é computado uma única vez. Para propriedades mutáveis, useDelegates.observableouDelegates.vetoable. - Ciclos de dependência em delegates: se dois delegates dependem um do outro, você pode causar um loop infinito ou
StackOverflowError. - Não considerar thread safety com
lazy: por padrão,lazyé sincronizado (LazyThreadSafetyMode.SYNCHRONIZED). Se você tem certeza de que a propriedade será acessada por uma única thread, uselazy(LazyThreadSafetyMode.NONE)para melhor performance.
Perguntas Frequentes
Qual a diferença entre delegation e herança? Herança cria uma relação “é um” (is-a), enquanto delegação cria uma relação “tem um” (has-a). Delegação é mais flexível porque permite trocar o comportamento em tempo de execução e evita os problemas da herança múltipla.
Posso delegar para uma propriedade mutável?
Sim, mas cuidado. Se você usa var para o objeto delegado, trocar a referência não atualiza a delegação, pois o by captura a referência no momento da construção.
O que é Delegates.vetoable?
É similar ao observable, mas permite rejeitar a mudança de valor. A lambda retorna Boolean: se retornar false, o valor não é atualizado.
Delegação de classe funciona com classes abstratas?
Não. A delegação com by só funciona com interfaces. Você não pode usar by para delegar a implementação de uma classe abstrata ou concreta.