В Kotlin делегирование поведения реализовано языковым механизмом ключевого слова by, напрямую в сигнатуре класса. Это позволяет автоматически передавать вызовы методов интерфейса (или нескольких интерфейсов) другому объекту с реализацией, снижая бойлерплейт и облегчая композицию.
Появление делегирования интерфейса — попытка устранить ограничения и недостатки множественного наследования. Это идея "composition over inheritance" — делегируем поведение, не прибегая к иерархии классов. Заимствовано из языков, где композиция более популярна (например, Go, Scala).
В Java и других языках часто приходится создавать интерфейс и вручную реализовывать каждый метод, передавая логику другому полю (Object Adapter pattern), что быстро устаревает при росте числа методов.
Kotlin позволяет декларативно делегировать интерфейс с помощью by:
interface Logger { fun log(msg: String) } class ConsoleLogger: Logger { override fun log(msg: String) = println(msg) } class Service(logger: Logger): Logger by logger { fun doWork() { log("Work started") // ... } } val service = Service(ConsoleLogger()) service.doWork()
Ключевые особенности:
Что происходит, если в классе Service добавить свой собственный метод интерфейса с той же сигнатурой?
Собственная реализация "перебивает" делегируемую — то есть преобладает метод, определённый явно в классе:
class Service(logger: Logger): Logger by logger { override fun log(msg: String) = println("PREFIX: $msg") }
Может ли один класс делегировать несколько интерфейсов разным объектам?
Да, класс может реализовать и делегировать несколько интерфейсов разным объектам, но каждый интерфейс делегируется одному объекту:
class Service( logger: Logger, tracker: Tracker ): Logger by logger, Tracker by tracker
Чем делегирование интерфейса отличается от делегирования свойства через by?
Класс вручную реализует интерфейс, каждый метод вызывает делегат, при добавлении новых методов забывают обновить проксирование, что приводит к ошибкам.
Плюсы:
Минусы:
Используется языковое делегирование, только нестандартные методы реализуются внутри класса, новый функционал добавляется без бо́льших изменений.
Плюсы:
Минусы: