Ответ
Делегаты свойств (by) позволяют делегировать геттеры/сеттеры свойств другому объекту. Примеры:
Lazy инициализация:
val heavyObject: HeavyClass by lazy {
HeavyClass() // создастся только при первом обращении
}
Observable свойства:
var name: String by Delegates.observable("") { _, old, new ->
println("$old -> $new")
}
Хранение в Map:
class User(val map: Map<String, Any>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "Alex", "age" to 25))
Кастомный делегат:
class LoggingDelegate<T>(private var value: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value.also {
println("Read ${property.name} = $it")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("Set ${property.name} = $newValue")
value = newValue
}
}
var counter: Int by LoggingDelegate(0) Ответ 18+ 🔞
А, делегаты свойств! Ну это же просто ёперный театр, я тебе скажу. В Котлине такая штука есть, которая позволяет отдать геттеры и сеттеры какому-то другому объекту на откуп. Вместо того чтобы самому писать кучу кода, ты просто говоришь: "На, братан, разбирайся". Удобно, блядь, до безобразия.
Вот смотри, самый прикол — это ленивая инициализация. Представь, у тебя есть какой-то тяжелый объект, который создаётся долго и ресурсов жрёт, как не в себя. Так вот, зачем его создавать сразу, если он может вообще не понадобиться? Берёшь by lazy, и всё.
val heavyObject: HeavyClass by lazy {
HeavyClass() // создастся только при первом обращении
}
Вот тут магия: этот HeavyClass() вызовется один раз и только тогда, когда ты впервые попробуешь прочитать heavyObject. До этого — нихуя не происходит. Экономия налицо, чувак.
Дальше — наблюдаемые свойства. Это когда тебе надо следить, как меняется какая-то переменная. Раньше бы пришлось городить огород с сеттерами и вызовами колбэков. А теперь? Delegates.observable, и делов-то!
var name: String by Delegates.observable("") { _, old, new ->
println("$old -> $new")
}
Поменял name — и тут же в консольку прилетело, что было и что стало. Красота, ёпта. Для логирования или всякой реактивной херни — самое то.
А вот это вообще пиздец как круто — хранение свойств прямо в мапе. Смотри, не отходя от кассы:
class User(val map: Map<String, Any>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "Alex", "age" to 25))
Ты создаёшь объект User, передаёшь ему мапу, а свойства name и age автоматом из этой мапы читаются. Как будто они там живут. Это ж какая экономия на бойлерплейте, ядрёна вошь! JSON'ы парсить, конфиги читать — идеально.
Ну и если тебе мало встроенных делегатов, можно свой, кастомный, накодить. Выглядит, конечно, немного страшновато с этими getValue и setValue, но ничего, разберёшься.
class LoggingDelegate<T>(private var value: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value.also {
println("Read ${property.name} = $it")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("Set ${property.name} = $newValue")
value = newValue
}
}
var counter: Int by LoggingDelegate(0)
Смысл в чём? Каждый раз, когда ты читаешь или пишешь counter, в консоль летит сообщение. Для отладки — просто бомба. Сразу видно, кто, когда и какую хуйню творит с твоей переменной.
В общем, делегаты — это мощный инструмент. Сначала кажется, что какая-то магия, непонятная хуйня. Но как вникнешь — понимаешь, что жить без них уже не можешь. Главное — не переборщить, а то можно так заделегировать, что потом сам черт ногу сломит.