Делегированная недвижимость в Котлине
1. Вступление
Язык программирования Kotlin имеет встроенную поддержку свойств класса.
Свойства обычно поддерживаются соответствующими полями, но это не всегда должно быть так - если они правильно выставлены внешнему миру, они все еще могут рассматриваться как свойства.
Этого можно достичь, обработав это в геттерах и сеттерах, или используя мощностьDelegates.
2. Что такое делегированные свойства?
Simply put, delegated properties are not backed by a class field and delegate getting and setting to another piece of code. Это позволяет абстрагировать делегированные функции и совместно использовать их между несколькими похожими свойствами, например хранение значений свойств на карте вместо отдельных полей.
Делегированные свойства используются путем объявления свойства и делегата, который он использует. The by keyword indicates that the property is controlled by the provided delegate instead of its own field.с
Например:
class DelegateExample(map: MutableMap) {
var name: String by map
}
При этом используется тот факт, чтоMutableMap сам является делегатом, что позволяет вам рассматривать его ключи как свойства.
3. Стандартные делегированные свойства
Стандартная библиотека Kotlin поставляется с набором стандартных делегатов, которые готовы к использованию.
Мы уже видели пример использованияMutableMap для поддержки изменяемого свойства. Точно так же вы можете поддерживать неизменяемое свойство с помощьюMap - разрешая доступ к отдельным полям как к свойствам, но не изменять их.
The lazy delegate allows the value of a property to be computed only on first access and then cached. Это может быть полезно для свойств, вычисление которых может быть дорогостоящим и которые могут вам никогда не понадобиться - например, при загрузке из базы данных:
class DatabaseBackedUser(userId: String) {
val name: String by lazy {
queryForValue("SELECT name FROM users WHERE userId = :userId", mapOf("userId" to userId)
}
}
The observable delegate allows for a lambda to be triggered any time the value of the property changes, например, разрешение уведомлений об изменениях или обновление других связанных свойств:
class ObservedProperty {
var name: String by Delegates.observable("") {
prop, old, new -> println("Old value: $old, New value: $new")
}
}
4. Создание ваших делегатов
Будут времена, когда вы захотите написать своих делегатов, а не использовать уже существующие. This relies on writing a class that extends one of two interfaces – ReadOnlyProperty or ReadWriteProperty.с
Оба этих интерфейса определяют метод с именемgetValue, который используется для предоставления текущего значения делегированного свойства при его чтении. Это принимает два аргумента и возвращает значение свойства:
-
thisRef - ссылка на класс, в котором находится свойство
-
property - отражение описания делегируемого свойства
ИнтерфейсReadWriteProperty дополнительно определяет метод с именемsetValue, который используется для обновления текущего значения свойства при его записи. Это принимает три аргумента и не имеет возвращаемого значения:
-
thisRef - ссылка на класс, в котором находится свойство
-
property - описание отражения делегируемого свойства
-
value - Новое значение свойства
В качестве примера напишем делегат, который всегда работает с подключением к базе данных, а не с локальными полями:
class DatabaseDelegate(readQuery: String, writeQuery: String, id: Any) : ReadWriteDelegate {
fun getValue(thisRef: R, property: KProperty<*>): T {
return queryForValue(readQuery, mapOf("id" to id))
}
fun setValue(thisRef: R, property: KProperty<*>, value: T) {
update(writeQuery, mapOf("id" to id, "value" to value))
}
}
Это зависит от двух функций верхнего уровня для доступа к базе данных:
-
queryForValue - это требует некоторого SQL и некоторого связывания и возвращает первое значение
-
update - для этого требуется некоторый SQL, а некоторые связываются и обрабатывают его как инструкцию UPDATE
Затем мы можем использовать это, как любой обычный делегат, и наш класс будет автоматически поддерживаться базой данных:
class DatabaseUser(userId: String) {
var name: String by DatabaseDelegate(
"SELECT name FROM users WHERE userId = :id",
"UPDATE users SET name = :value WHERE userId = :id",
userId)
var email: String by DatabaseDelegate(
"SELECT email FROM users WHERE userId = :id",
"UPDATE users SET email = :value WHERE userId = :id",
userId)
}
5. Резюме
Делегирование свойств является мощной техникой, которая позволяет вам писать код, который контролирует другие свойства, и помогает легко использовать эту логику среди различных классов. Это позволяет использовать надежную, многократно используемую логику, которая выглядит и ощущается как обычный доступ к свойству.
Полностью рабочий пример для этой статьи можно найтиover on GitHub.