Существует несколько основных видов свойств, которые мы реализовываем каждый раз вручную в случае их надобности. Однако намного удобнее было бы реализовать их раз и навсегда и положить в какую-нибудь библиотеку. Сегодня рассмотрим 4 интересных так называемых delegate properties, которые могут быть полезны при разработке Android-приложений на языке Kotlin.
Итак мы разберем следующие делегированные свойства:
- Lazy
- Observable
- Vetoable
- notNull
Если о Lazy многие слышали или даже использовали, то о других, порой не знают даже опытные разработчики.
Lazy
lazy()
это функция, которая принимает лямбду и возвращает экземпляр класса Lazy<T>
, который служит делегатом для реализации ленивого свойства: первый вызов get()
запускает лямбда-выражение, переданное lazy()
в качестве аргумента, и запоминает полученное значение, а последующие вызовы просто возвращают вычисленное значение.
val str by lazy { "I'm lazily initialized" }
Тут может возникнуть вопрос (и такие вопросы часто возникают на собеседовании), а именно, синхронизированы ли вычисления при обработке кода несколькими потоками? По умолчанию вычисление ленивых свойств синхронизировано: значение вычисляется только в одном потоке выполнения, и все остальные потоки могут видеть одно и то же значение. Однако, если вы уверены, что данный код будет выполнять только в одном потоке, то вы можете управлять способом вычисления lazy следующими параметрами, передав один из них в качестве аргумента функции lazy()
enum class LazyThreadSafetyMode{ SYNCHRONIZED, PUBLICATION, NONE }
- Если вы не передадите никакое значение в lazy() то будет использоваться потокобезопасный режим
SYNCHRONIZED
. - Режим
PUBLICATION
означает, что блок в lazy() будет доступен нескольким потокам, однако значение будет возвращено первое, которое подсчитается. - Режим
NONE
не является потокобезопасным и должен использоваться крайне редко – когда вы действительно уверены, что данный блок кода будет исполняться в одном потоке.
Observable
Данное свойство позволяет вызывать некую функцию каждый раз, когда значение изменилось. Функция Delegates.observable()
принимает два аргумента: начальное значение свойства и обработчик (лямбда), который вызывается при изменении свойства. У обработчика три параметра: описание свойства, которое изменяется, старое значение и новое значение.
var observable by Delegates.observable(1) { prop, oldVal, newVal -> println("Observable property changed from $oldVal to $newVal") }
Теперь, если мы изменим значение, то будет вызвана лямбда
// Изменение значение напечатает Observable property changed from 1 // to 0 observable = 0
Vetoable
Это свойство похоже на Observable. С помощью vetoable можно узнать, когда значение меняется и тем самым, например добавить проверку перед присваиванием значения. Например:
var vetoable by Delegates.vetoable(1) { prop, oldVal, newVal -> return@vetoable newVal <= 10 }
Если мы попробуем поменять значение, то изменение будет заблокировано, так как нам подходят значения <= 10
//change is blocked vetoable = 100
Если лямбда вернет true – то все ок, значение можно менять
//changed to 0 vetoable = 0
notNull
Это свойство похоже на lateinit. Оно позволяет объявить свойство без начального значения. Если попытаться прочитать значение до того, как присвоили значение – будет брошено исключение.
var notNull by Delegates.notNull()
Итак, мы рассмотрели 4 delegate properties Lazy, Observable, Vetoable и notNull. Надеюсь данный материал был полезен и полученные знания сделают ваш код более изящным и читабельным. Также рекомендую взглянуть на документацию.