Kotlin что такое делегат

Kotlin M5.3: Delegated Properties

Не так давно мы выпустили очередной майлстоун языка программировани Kotlin, M5.3.
В этот релиз вошло довольно много разных изменений: от рефакторингов до новых возможностей в языке. Здесь я хочу рассказать про самое интересное изменение: поддержку делегированных свойств (delegated properties).

  • ленивые свойства (lazy properties): значение вычисляется один раз, при первом обращении;
  • свойства, на события об изменении которых можно подписаться (observable properties);
  • свойства, хранимые в Map’е, а не в отдельных полях;
  • .

Делегированные свойства

Появился новый синтаксис: теперь после типа свойства можно написать «by ». Выражение после «by» является делегатом: вызовы геттера и сеттера для этого свойства будут делегированы значению этого выражения. Мы не требуем, чтобы делегат реализовывал какой-то интерфейс, достаточно, чтобы у него были функции get() и set() с определенной сигнатурой:

class Delegate() < fun get(thisRef: Any?, prop: PropertyMetadata): String < return "$thisRef, thank you for delegating '$' to me!" > fun set(thisRef: Any?, prop: PropertyMetadata, value: String) < println("$value has been assigned") >> 

(Некоторых пугает отсутствие требования реализовывать интерфейс. Не бойтесь, если вам так спокойнее, вот он, даже два — реализуйте 🙂 )

Если мы читаем значение свойства p, вызывается функция get() из класса Delegate, причем первым параметром ей передается тот объект, у которого запрашивается свойство, а вторым — объект-описание самого свойства p (у него можно, в частности, узнать имя свойства):

Этот пример выведет «Example@33a17727, thank you for delegating ‘p’ to me!».

Аналогично, когда присходит запись свойства, вызывается set(). Два первых параметра — такие же как у get(), а третий — присваиваемое значение свойства:

Этот пример выведет «NEW has been assigned to ‘p’ in Example@33a17727».

Вы, наверное, уже догадались, как можно реализовать ленивые свойства и пр.? Можете попробовать сделать это сами, но бОльшая часть всего этого уже реализована в стандартной библиотеке Kotlin. Наиболее употребительные делегаты определены в объекте kotlin.properties.Delegates.

Ленивые свойства

import kotlin.properties.Delegates class LazySample < val lazy: String by Delegates.lazy < println("computed!") "Hello" >> 

Функция Delegates.lazy() возвращает объект-делегат, реализующий ленивое вычисление значения свойства: первый вызов get() запускает лямбда-выражение, переданное lazy() в качестве аргумента, и запоминает полученное значение; последующие вызовы просто возвращают запомненное.

Если Вы хотите использовать ленивые свойства в многопоточной программе, воспользуйтесь функцией blockingLazy(): она гарантирует, что значение будет вычислено ровно одним потоком и корректно опубликовано.

Читайте также:  Eclipse java how to install

Observable свойства

class User < var name: String by Delegates.observable("") < d, old, new ->println("$old -> $new") > > 

Функция observable() принимает два аргумента: начальное значение свойства и обработчик (лямбда-выражение), который вызывается при каждом присваивании. У обработчика три параметра: описание свойства, которое изменяется, старое значение и новое значение. Если Вам нужно иметь возможность запретить присваивание некоторых значений, используйте функцию vetoable() вместо observable().

Свойства без инициализаторов

Относительно неожиданное применение делегатов: многие пользователи спрашивают: «Как объявить not-null свойство, если у меня нет значения, которым его проинициализировать (я его потом присвою)?». Kotlin не разрешает объявлять неабстрактные свойства без инициализаторов:

Можно было бы присвоить null, то тогда тип будет уже не «Bar», а «Bar?», и при каждом обращении нужно будет обрабатывать случай нулевой ссылки… Теперь можно обойтись делегатом:

Если это свойство считать до первого присваивания, делегат бросит исключение. После инициализации он просто возвращает ранее записанное значение.

Хранение свойств в хеш-таблице

Последний пример из библиотеки: хранение свойств в Map. Это полезно в «динамическом» коде, например, при работе с JSON:

Конструктор этого класса принимает map:

val user = User(mapOf( "name" to "John Doe", "age" to 25 )) 

Делегаты вычисляют значения по стоковым ключам — именам свойств:

println(user.name) // Prints "John Doe" println(user.age) // Prints 25 

Изменяемые свойства (var) поддержиаются с помощью функции mapVar(): значения записываются по таким же ключам (для этого нужен MutableMap, а не просто Map).

Заключение

Мы поговорили о делегированных свойствах, механизме, который добавляет новую степень свободы в язык и дает возможность задавать свою семантику операциям над свойствами. Я показал примеры, лежащие на поверхности, но, наверняка, есть еще немало способов применить этот механизм, так что добро пожаловать: придумывайте и реалиуйте!

P.S. Про другие новинки в Kotlin M5.3 можно почитать здесь (по-английски).

Источник

Делегирование

Шаблон делегирования является хорошей альтернативой наследованию, и Kotlin поддерживает его нативно, освобождая вас от необходимости написания шаблонного кода.

Класс Derived может реализовать интерфейс Base , делегируя все свои public члены указанному объекту.

interface Base < fun print() >class BaseImpl(val x: Int) : Base < override fun print() < print(x) >> class Derived(b: Base) : Base by b fun main()

Ключевое слово by в оглавлении Derived , указывает, что b будет храниться внутри экземпляра Derived , и компилятор сгенерирует все методы из Base , которые при вызове будут переданы объекту b .

Читайте также:  Run on main thread python

Переопределение члена интерфейса, реализованного делегированием

Переопределения работают так, как вы ожидаете: компилятор будет использовать ваши override реализации вместо реализаций в объекте делегата. Если вы хотите добавить override fun printMessage() < print("abc") >в Derived , программа будет печатать abc вместо 10 при вызове printMessage .

interface Base < fun printMessage() fun printMessageLine() >class BaseImpl(val x: Int) : Base < override fun printMessage() < print(x) >override fun printMessageLine() < println(x) >> class Derived(b: Base) : Base by b < override fun printMessage() < print("abc") >> fun main()

Однако обратите внимание, что члены, переопределенные таким образом, не вызываются из членов делегата, который может получить доступ только к своим собственным реализациям членов интерфейса.

interface Base < val message: String fun print() >class BaseImpl(val x: Int) : Base < override val message = "BaseImpl: x = $x" override fun print() < println(message) >> class Derived(b: Base) : Base by b < // Это свойство недоступно из `b` реализации `print` override val message = "Message of Derived" >fun main()

Источник

Kotlin что такое делегат

Делегированные свойства позволяют делегировать получение или присвоение их значения во вне — другому классу. Это позволяет нам добавить некоторую дополнительную логику при операции со свойствами, например, логгирование, какую-то предобработку и т.д.

Формальный синтаксис делегированного свойства:

val/var имя_свойства: тип_данных by выражение

После типа данных свойства идет ключевое слово by , после которого указывается выражение. Выражение представляет класс, который условно называется делегатом. Делегаты свойств могут не применять никаких интерфейсов, однако они должны предоставлять функции getValue() и setValue() . А выполнение методов доступа get() и set() , которые есть у свойства, делегируется функциям getValue() и setValue() класса делегата.

Стоит отметить, что мы не можем объявлять делегированные свойства в первичном конструкторе.

Делегированные свойства для чтения

Для свойств только для чтения (то есть val -свойств), делегат должен предоставлять функцию getValue() , которая принимает следующие параметры:

  • thisRef : должен представлять тот же тип, что и свойство, к которому применяется делегат. Это может быть и родительский тип.
  • property : должен представлять тот же тип KProperty или его родительский тип

При этом функция getValue() должна возвращать результат того же типа, что и тип свойства (либо его производного типа).

import kotlin.reflect.KProperty fun main() < val tom = Person() println(tom.name) // Tom val bob = Person() println(bob.name) // Tom >class Person < val name: String by LoggerDelegate() >class LoggerDelegate < operator fun getValue(thisRef: Person, property: KProperty): String < println("Запрошено свойство: $") return "Tom" > >

Здесь класс Person определяет свойство name , которое является делегированным — оно делегирует операцию получения значения функции getValue() класса LoggerDelegate .

Читайте также:  Таблица

Поскольку свойство определено в классе Person, то первый параметр функции getValue() представляет тип Person. Благодаря этому мы можем выудить из этого параметра какую-то дополнительную информацию об объекте, если она необходима.

Поскольку свойство представляет тип String , то функция также возвращает значение типа String — это то значение, которое будет возвращаться самим свойством name . В данном случае возвращается строка «Tom». То есть при каждом обращении к свойству name объекта Person будет возвращаться строка «Tom».

Теперь немного видоизменим пример:

import kotlin.reflect.KProperty fun main() < val tom = Person("Tom") println(tom.name) val bob = Person("Bob") println(bob.name) >class Person(_name: String) < val name: String by LoggerDelegate(_name) >class LoggerDelegate(val personName: String) < operator fun getValue(thisRef: Person, property: KProperty): String < println("Запрошено свойство $") println("Устанавливаемое значение: $personName") return personName > >

Теперь первичный конструктор Person принимает устанавливаемое значение для свойства name. Далее оно передается в конструктор классу LoggerDelegate, который использует его для логгирования на консоль. И в конце возвращает его в качестве значения свойства name.

Изменяемые свойства

Для изменяемых свойств ( var -свойств) делегат должен также предоставить функцию setValue() , которая принимает следующие параметры:

  • thisRef : должен представлять тот же тип, что и свойство, к которому применяется делегат. Это может быть и родительский тип.
  • property : должен представлять тот же тип KProperty или его родительский тип
  • value : должен представлять тот же тип, что и свойство, или его родительский тип
import kotlin.reflect.KProperty fun main() < val tom = Person("Tom", 37) println(tom.age) //37 tom.age = 38 println(tom.age) //38 tom.age = -139 println(tom.age) //38 >class Person(val name: String, _age: Int) < var age: Int by LoggerDelegate(_age) >class LoggerDelegate(private var personAge: Int) < operator fun getValue(thisRef: Person, property: KProperty): Int < return personAge >operator fun setValue(thisRef: Person, property: KProperty, value: Int) < println("Устанавливаемое значение: $value") if(value >0 && value < 110) personAge = value >>

Здесь класс Person определяет делегированное свойство age . Оно делегирует установку и получение значения классу LoggerDelegate и его функциям getValue() и setValue() . Само значение сохраняется в свойстве personAge класса LoggerDelegate. Функция getValue() просто возвращает значение это свойства.

Функция setValue() с помощью третьего параметра — value , которое представляет тот же тип, что и свойство — тип Int , получает устанавливаемое значение. И если оно соответствует некоторому диапазону, то передает в свойство personAge.

Консольный вывод программы:

37 Устанавливаемое значение: 38 38 Устанавливаемое значение: -139 38

Источник

Оцените статью