Delegation
https://kotlinlang.org/docs/delegation.html |
https://kotlinlang.org/docs/delegated-properties.html |
Delegates
Делегирование представляет паттерн объектно-ориентированного программирования, который позволяет одному объекту делегировать/перенаправить все запросы другому объекту. В определенной степени делегирование может выступать альтернативой наследованию.
• Бывают 2 видов: на класс и на поле.
interface Messenger {
fun send(message: String)
}
class InstantMessenger: Messenger {
override fun send(message: String) {
println(message)
}
}
class SmartPhone(name: String, messenger: Messenger): Messenger by messenger
val telegram = InstantMessenger()
val pixel = SmartPhone("Pixel 9", telegram)
pixel.send("Hello World")
Delegated Properties
Делегированные свойства позволяют делегировать получение или присвоение их значения во вне - другому классу. Это позволяет нам добавить некоторую дополнительную логику при операции со свойствами, например, логгирование, какую-то предобработку и т.д
class Person(personName: String) {
var name: String by LoggerDelegate(personName)
}
class LoggerDelegate(private var personName: String) {
operator fun getValue(thisRef: Person, property: KProperty<*>): String {
return personName
}
operator fun setValue(thisRef: Person, property: KProperty<*>, value: String) {
personName = value
}
}
val tom = Person("Tom")
tom.name = "Bob"
println(tom.name)
ReadOnlyProperty
Используется для создания делегатов только для чтения (для свойств val
).
class LazyStringDelegate: ReadOnlyProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Computed Value"
}
}
val myProperty by LazyStringDelegate()
ReadWriteProperty
Используется для создания делегатов для свойств с возможностью записи (var
). Наследуется от ReadOnlyProperty
и добавляет метод setValue
для управления записью данных.
class ObservableProperty(var value: Int): ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
println("Value of ${property.name} is changing from $this.value to $value")
this.value = value
}
}
var myVar by ObservableProperty(10)
PropertyDelegateProvider
Интерфейс, позволяющий создать делегаты, которые могут учитывать дополнительные параметры на этапе инициализации. Это полезно, когда нужно передать начальные данные в делегат или выполнить дополнительную логику для настройки делегата в зависимости от контекста.
class ConfigurableDelegateProvider(
private val initialValue: Int
): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, Int>> {
override fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadWriteProperty<Any?, Int> {
println("Initializing property '${property.name}' with initial value $initialValue")
return ConfigurableDelegate(initialValue)
}
}
class ConfigurableDelegate(
var value: Int
): ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
this.value = value
}
}
class Example {
var prop1 by ConfigurableDelegateProvider(10)
var prop2 by ConfigurableDelegateProvider(20)
}
by
Используется для делегирования свойств или функций к другим объектам. Оно позволяет передать ответственность за реализацию или хранение значения другому объекту, минимизируя дублирование кода и повышая гибкость.
val lazyValue: String by lazy {
println("Computed!")
"Hello, Kotlin"
}
lazy
Инициализирует свойство лениво. Это значит, что свойство будет инициализировано только при первом обращении к нему.
• LazyThreadSafetyMode.SYNCHRONIZED
Используется по умолчанию. Значение вычисляется только в одном потоке выполнения, и все остальные потоки могут видеть одно и то же значение.
• LazyThreadSafetyMode.PUBLICATION
Используется, когда синхронизация не требуется, тогда несколько потоков смогут исполнять вычисление одновременно.
• LazyThreadSafetyMode.NONE
Используется, если инициализация всегда будет происходить в одном потоке исполнения. Не гарантирует никакой потокобезопасности и связанных с этим дополнительных затрат.
val name: String by lazy(::getName)
val name: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
getName()
}
observable
Позволяет следить за изменениями свойства и выполнять определенные действия при каждом изменении.
var observableValue: String by Delegates.observable("Initial") { prop, old, new ->
println("Property ${prop.name} changed from $old to $new")
}
observableValue = "Changed" // Property observableValue changed from Initial to Changed
vetoable
Позволяет проверять новые значения свойства перед его изменением и решать, следует ли принять это значение или нет.
var vetoableValue: Int by Delegates.vetoable(0) { _, old, new ->
new >= 0 // Разрешаем только положительные значения
}
vetoableValue = 10 // Успешно изменено
println(vetoableValue) // 10
vetoableValue = -1 // Не изменяется, так как значение отрицательное
println(vetoableValue) // 10
notNull
Используется для свойств, которые будут инициализированы позже, но которые никогда не должны быть null
.
var name: String by Delegates.notNull()
fun initializeName(value: String) {
name = value
}
fun printName() {
println(name) // Если name не инициализирован, произойдет ошибка
}
map
Позволяет связывать свойства объекта с элементами Map
. Это удобно для динамического маппинга данных.
class User(val map: MutableMap<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mutableMapOf("name" to "John", "age" to 30))
println(user.name) // John
println(user.age) // 30
user.name = "Jane"
println(user.map["name"]) // Jane
Вопросы на собесе (6)
- Какие виды делегатов есть в Kotlin?
На класс и на поле.
- Какие стандартные делегаты есть в Kotlin?
•lazy()
•
observable()
•
vetoable()
•
notNull()
•
map()
- Как работает делегат на класс?
Позволяет делегировать реализацию свойств или методов другому объекту. Для этого нужно использовать ключевое слово
by
, указывая на делегирующий объект.
- На каком потоке исполняется lazy?
По умолчанию инициализация
lazy
выполняется на том потоке, где впервые запрашивается его значение. Однако, если используетсяLazyThreadSafetyMode.PUBLICATION
илиLazyThreadSafetyMode.SYNCHRONIZED
, инициализация может быть потокобезопасной.
- Является ли lazy потокобезопасным?
Да,
lazy
является потокобезопасным по умолчанию, используяLazyThreadSafetyMode.SYNCHRONIZED
. Это означает, что при первом доступе к значению инициализация будет безопасной для потоков.
- Как создать свой собственный делегат?
Реализовать интерфейс
ReadOnlyProperty
илиReadWriteProperty
. Затем необходимо определить методыgetValue
и (при необходимости)setValue
.