Делегированные свойства

Последнее обновление: 02.06.2021

Делегированные свойства позволяют делегировать получение или присвоение их значения во вне - другому классу. Это позволяет нам добавить некоторую дополнительную логику при операции со свойствами, например, логгирование, какую-то предобработку и т.д.

Формальный синтаксис делегированного свойства:

val/var имя_свойства: тип_данных by выражение

После типа данных свойства идет ключевое слово by, после которого указывается выражение. Выражение представляет класс, который условно называется делегатом. Делегаты свойств могут не применять никаких интерфейсов, однако они должны предоставлять функции getValue() и setValue(). А выполнение методов доступа get() и set(), которые есть у свойства, делегируется функциям getValue() и setValue() класса делегата.

Стоит отметить, что мы не можем объявлять делегированные свойства в первичном конструкторе.

Делегированные свойства для чтения

Для свойств только для чтения (то есть val-свойств), делегат должен предоставлять функцию getValue(), которая принимает следующие параметры:

  • thisRef: должен представлять тот же тип, что и свойство, к которому применяется делегат. Это может быть и родительский тип.

  • property: должен представлять тот же тип KProperty<*> или его родительский тип

При этом функция getValue() должна возвращать результат того же типа, что и тип свойства (либо его производного типа).

Рассмотрим на примере:

import kotlin.reflect.KProperty

fun main() {

    val tom = Person()
    println(tom.name)	// Tom
	
	val bob = Person()
    println(bob.name)	// Tom
}
class Person{
    val name: String by LoggerDelegate()
}
class LoggerDelegate {
    operator fun getValue(thisRef: Person, property: KProperty<*>): String {
        println("Запрошено свойство: ${property.name}")
        return "Tom"
    }
}

Здесь класс Person определяет свойство name, которое является делегированным - оно делегирует операцию получения значения функции getValue() класса LoggerDelegate.

Поскольку свойство определено в классе Person, то первый параметр функции getValue() представляет тип Person. Благодаря этому мы можем выудить из этого параметра какую-то дополнительную информацию об объекте, если она необходима.

Поскольку свойство представляет тип String, то функция также возвращает значение типа String - это то значение, которое будет возвращаться самим свойством name. В данном случае возвращается строка "Tom". То есть при каждом обращении к свойству name объекта Person будет возвращаться строка "Tom".

Теперь немного видоизменим пример:

import kotlin.reflect.KProperty
fun main() {
    val tom = Person("Tom")
    println(tom.name)   

    val bob = Person("Bob")
    println(bob.name)
}
class Person(_name: String){
    val name: String by LoggerDelegate(_name)
}
class LoggerDelegate(val personName: String) {
    operator fun getValue(thisRef: Person, property: KProperty<*>): String {
        println("Запрошено свойство ${property.name}")
        println("Устанавливаемое значение: $personName")
        return personName
    }
}

Теперь первичный конструктор Person принимает устанавливаемое значение для свойства name. Далее оно передается в конструктор классу LoggerDelegate, который использует его для логгирования на консоль. И в конце возвращает его в качестве значения свойства name.

Изменяемые свойства

Для изменяемых свойств (var-свойств) делегат должен также предоставить функцию setValue(), которая принимает следующие параметры:

  • thisRef: должен представлять тот же тип, что и свойство, к которому применяется делегат. Это может быть и родительский тип.

  • property: должен представлять тот же тип KProperty<*> или его родительский тип

  • value: должен представлять тот же тип, что и свойство, или его родительский тип

Рассмотрим на примере:

import kotlin.reflect.KProperty

fun main() {

    val tom = Person("Tom", 37)
    println(tom.age)	//37
    tom.age = 38
    println(tom.age)	//38
    tom.age = -139
    println(tom.age)	//38

}
class Person(val name: String, _age: Int){
    var age: Int by LoggerDelegate(_age)
}
class LoggerDelegate(private var personAge: Int) {
    operator fun getValue(thisRef: Person, property: KProperty<*>): Int{
        return personAge
    }
    operator fun setValue(thisRef: Person, property: KProperty<*>, value: Int){
        println("Устанавливаемое значение: $value")
        if(value > 0 && value < 110) personAge = value
    }
}

Здесь класс Person определяет делегированное свойство age. Оно делегирует установку и получение значения классу LoggerDelegate и его функциям getValue() и setValue(). Само значение сохраняется в свойстве personAge класса LoggerDelegate. Функция getValue() просто возвращает значение это свойства.

Функция setValue() с помощью третьего параметра - value, которое представляет тот же тип, что и свойство - тип Int, получает устанавливаемое значение. И если оно соответствует некоторому диапазону, то передает в свойство personAge.

Консольный вывод программы:

37
Устанавливаемое значение: 38
38
Устанавливаемое значение: -139
38
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850