English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Паттерн доверительного доступа является базовой техникой в软件-дизайне. В паттерне доверительного доступа участвуют два объекта, которые обрабатывают один и тот же запрос, объект,接受的 запрос, передает его для обработки другому объекту.
Kotlin напрямую поддерживает паттерн доверительного доступа, что делает его более изящным и простым. Kotlin реализует доверительный доступ через ключевое слово by.
Доверительный класс означает, что метод, определенный в классе, фактически вызывается методом объекта другого класса.
В следующем примере производный класс Derived наследует все методы интерфейса Base и доверяет переданному объекту Base выполнять эти методы.
// Создание интерфейса interface Base { fun print() {} // Реализующий этот интерфейс доверительный класс class BaseImpl(val x: Int) : Base { override fun print() { print(x) } {} // Создание доверительного класса через ключевое слово by class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // вывод 10 {}
В объявлении Derived, подзюсла by означает, что b хранится в объекте примера Derived, и компилятор будет генерировать все методы, наследующиеся от интерфейса Base, и будет передавать вызовы b.
Доверительное свойство означает, что значение свойства某个 класса не определяется напрямую в классе, а передается доверительному классу, что позволяет統一 управлять свойствами класса.
Грамматика доверительного свойства:
val/var <имя свойства>: <тип> by <выражение>
var/val: тип свойства (изменяемый/только для чтения)
Имя свойства: имя свойства
Тип: тип данных свойства
Выражение: класс доверительного代理
Выражение после ключевого слова by является déléгатом, метод get() свойства (и метод set() для свойств с установкой значения) будет déléгирован методам getValue() и setValue() этого объекта. Свойства-дéléгаты не должны реализовывать ни одного интерфейса, но должны предоставлять функцию getValue() (для свойств var также необходимо предоставить функцию setValue()).
Этот класс должен содержать функции getValue() и setValue(), и параметр thisRef является объектом класса, который выполняет déléгирование, prop - объект свойства, которое déléгируется.
import kotlin.reflect.KProperty // класс, определяющий класс с свойствами-дéléгатами class Example { var p: String by Delegate() {} // класс déléгирования class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, здесь déléгировано свойство ${property.name}" {} operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef свойство ${property.name} присвоено значению $value") {} {} fun main(args: Array<String>) { val e = Example() println(e.p) // доступ к свойству, вызов функции getValue() e.p = "w3codebox" // вызов функции setValue() println(e.p) {}
Результат вывода:
Example@433c675d, где déléгировано свойство p Свойство p Example@433c675d присвоено значению w3codebox Example@433c675d, где déléгировано свойство p
Стандартная библиотека Kotlin уже содержит множество фабричных методов для реализации свойств-дéléгатов.
lazy() - это функция, которая принимает Лямбда-выражение в качестве параметра и возвращает функцию примера Lazy<T>, которая может быть использована для реализации задержанных свойств: при первом вызове get() выполняется переданный лямбда-выражение, и результат записывается, при последующих вызовах get() просто возвращается сохраненный результат.
val lazyValue: String by lazy {}} println("computed!") // Первый вызов выводится, второй вызов не выполняется "Hello" {} fun main(args: Array<String>) { println(lazyValue) // Первый запуск, выполняется два раза, выводится выражение println(lazyValue) // Второй запуск, выводится только возвращаемое значение {}
Результат выполнения:
computed! Hello Hello
Observable можно использовать для реализации паттерна наблюдателя.
Функция Delegates.observable() принимает два параметра: первый - начальное значение, второй - обработчик событий изменения значения свойства.
После присвоения свойства выполняется обработчик событий, который имеет три параметра: присваиваемое свойство, старое значение и новое значение:
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("начальное значение") { prop, old, new -> println("Старое значение: $old -> Новое значение: $new") {} {} fun main(args: Array<String>) { val user = User() user.name = "первое присвоение" user.name = "второе присвоение" {}
Результат выполнения:
Старое значение: начальное значение -> Новое значение: первое присвоение Старое значение: первое присвоение -> Новое значение: второе присвоение
Одна из常见的 ситуаций - хранение значений свойств в карте. Это часто встречается в приложениях, которые выполняют действия, такие как парсинг JSON или другие "динамические" задачи. В этом случае, вы можете использовать пример карты в качестве доверенного агента для реализации доверительных свойств.
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map {} fun main(args: Array<String>) { // Конструктор принимает параметр карты val site = Site(mapOf( "name" to "Базовый учебник", "url" to "ru.oldtoolbag.com" )) // Чтение значения карты println(site.name) println(site.url) {}
Результат выполнения:
Базовый учебник ru.oldtoolbag.com
Если используется свойство var,则需要 заменить Map на MutableMap:
class Site(val map: MutableMap<String, Any?>) {}} val name: String by map val url: String by map {} fun main(args: Array<String>) { var map: MutableMap<String, Any?> = mutableMapOf( "name" to "Базовый учебник", "url" to "ru.oldtoolbag.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------") map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) {}
Результат выполнения:
Базовый учебник ru.oldtoolbag.com -------------- Google www.google.com
NotNull подходит для тех случаев, когда значение свойства не может быть определено на этапе инициализации.
class Foo { var notNullBar: String by Delegates.notNull<String>() {} foo.notNullBar = "bar" println(foo.notNullBar)
Следует отметить, что если свойство будет доступно до его назначения, то будет выброшено исключение.
Вы можете объявить локальные переменные как доверенные свойства. Например, вы можете сделать локальную переменную ленивой инициализацией:
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() {} {}
Переменная memoizedFoo будет вычисляться только при первом обращении. Если someCondition не выполнится, то переменная вообще не будет вычисляться.
Для только чтения свойств (то есть свойство val), его доверенное лицо должно предоставить функцию getValue(). Эта функция принимает следующие параметры:
thisRef — должен быть того же типа, что и владелец свойства (для расширяемых свойств — это тип, который расширяется) или его супертип
property — должно быть типа KProperty или его супертип
Эта функция должна возвращать тот же тип (или его подтип).
Для переменного (мутабельного) свойства (т.е. var свойство) его делегат, кроме функции getValue(), должен также предоставлять функцию с именем setValue(), которая принимает следующие параметры:
thisRef — должен быть того же типа, что и владелец свойства (для расширяемых свойств — это тип, который расширяется) или его супертип
property — должно быть типа KProperty или его супертип
new value — должен быть того же типа, что и свойство, или его супертип.
За каждым реализованным свойством делегата компилятор Kotlin генерирует вспомогательное свойство и делегирует ему. Например, для свойства prop генерируется скрытое свойство prop$delegate, а код доступов просто делегирует этому дополнительному свойству:
class C { var prop: Type by MyDelegate(): {} // Это код, генерируемый компилятором: class C { private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) {}
Компилятор Kotlin предоставляет все необходимые данные о prop в параметрах: первый параметр this ссылается на пример класса C, а this::prop является反射ным объектом типа KProperty, который описывает сам prop.
Определением оператора provideDelegate можно расширить логику создания свойства для переданного объекта. Если объект, используемый справа от by, определяет provideDelegate как член или расширяемую функцию, то вызывается эта функция для создания примера делегата свойства.
Одним из возможных использования provideDelegate является проверка的一致ности свойств при создании свойства (а не только в getter или setter).
Например, если нужно проверить имя свойства перед его привязкой, можно написать так:
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // Создание делегата: {} private fun checkProperty(thisRef: MyUI, name: String) { …… } {} fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) {}
Параметры provideDelegate такие же, как и getValue:
thisRef —— должен быть того же типа, что и тип собственника свойства (для расширяемых свойств — это тип расширяемого типа)
property —— должен быть типа KProperty или его супertype.
Во время создания примера MyUI вызывайте метод provideDelegate для каждого свойства и сразу выполняйте необходимые проверки.
Если нет возможности блокировать связь между拦截ором свойств и его делегатом, чтобы реализовать аналогичную функциональность, вам необходимо явно передавать имя свойства, что не очень удобно:
// Проверка имени свойства без использования функции “provideDelegate”: class MyUI { val image by bindResource(ResourceID.image_id, "image") val text by bindResource(ResourceID.text_id, "text") {} fun <T> MyUI.bindResource( id: ResourceID<T>, propertyName: String ): ReadOnlyProperty<MyUI, T> { checkProperty(this, propertyName): // Создание делегата: {}
В сгенерированном коде вызывается метод provideDelegate для инициализации вспомогательного свойства prop$delegate. Сравните код для свойства val prop: Type by MyDelegate() с кодом, сгенерированным выше (когда метод provideDelegate не существует):
class C { var prop: Type by MyDelegate(): {} // Этот код генерируется, когда функция “provideDelegate” доступна: // Сгенерированный код компилятором: class C { // Вызов “provideDelegate” для создания дополнительного свойства “delegate” private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) {}
Обратите внимание, что метод provideDelegate влияет только на создание вспомогательных свойств и не влияет на код, созданный для getter или setter.