В Kotlin есть встроенная поддержка делегированных свойств (delegated properties). Механизм by позволяет делегировать геттер/сеттер любого свойства специализированному объекту — делегату. Наиболее известные делегаты: lazy, observable, vetoable и кастомные.
Преимущества:
Пример пользовательского делегата:
class UpperCaseDelegate { private var value: String = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) { value = newValue.uppercase() } } class Person { var name: String by UpperCaseDelegate() }
Ограничения:
Можно ли использовать делегат, требующий доступа к контексту (например, Android Context), в свойствах объектов-компаньонов или top-level объекта?
Часто отвечают неверно, что "можно всегда, почему нет?"
Правильный ответ: Нет, потому что companion-объекты и top-level объекты инициализируются до инициализации экземпляров класса или приложения, что может привести к ошибкам, связанным с обращением к неинициализированному контексту. Делегаты, требующие доступа к экземпляру, должны использоваться только в свойствах класса.
История
Делегирование ленивой инициализации в Android-ViewModel: Программист вынес heavy-lazy делегат в companion object. В некоторых ситуациях (после обновления SDK) приложение стало падать на инициализации — контекст ещё не был доступен, а делегат уже выполнил свой "init".
История
Неправильная сериализация с делегатами: Применяли кастомный делегат для хранения данных, однако он содержал несериализируемые ссылки на контекст. При попытке сериализации возникали ошибки и потеря данных.
История
Observable делегат с ошибкой обратного вызова: Разработчик использовал Delegates.observable для контроля состояния, внутри лямбды присваивался новый value, что привело к зацикливанию и StackOverflowError на рантайме.