Приведи примеры делегатов свойств

Ответ

Делегаты свойств (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, в консоль летит сообщение. Для отладки — просто бомба. Сразу видно, кто, когда и какую хуйню творит с твоей переменной.

В общем, делегаты — это мощный инструмент. Сначала кажется, что какая-то магия, непонятная хуйня. Но как вникнешь — понимаешь, что жить без них уже не можешь. Главное — не переборщить, а то можно так заделегировать, что потом сам черт ногу сломит.