Делегированная недвижимость в Котлине

Делегированная недвижимость в Котлине

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.