Делегированные свойства позволяют делегировать получение или присвоение их значения во вне - другому классу. Это позволяет нам добавить некоторую дополнительную логику при операции со свойствами, например, логгирование, какую-то предобработку и т.д.
Формальный синтаксис делегированного свойства:
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