"delegates, delegates everywhere" Владимир Миронов
TRANSCRIPT
Delegates, Delegates everywhereМиронов Владимир
Делегирование
Делегирование● Очень часто используемый дизайн паттерн
Делегирование● Очень часто используемый дизайн паттерн● Kotlin поддерживает делегирование на уровне языка
Делегирование● Очень часто используемый дизайн паттерн● Kotlin поддерживает делегирование на уровне языка● Два вида делегирования в Kotlin
Делегирование● Очень часто используемый дизайн паттерн● Kotlin поддерживает делегирование на уровне языка● Два вида делегирования в Kotlin● Interface delegation
Делегирование● Очень часто используемый дизайн паттерн● Kotlin поддерживает делегирование на уровне языка● Два вида делегирования в Kotlin● Interface delegation● Property delegation
Properties
Properties● Понятие field отсутсвует на уровне языка
Properties● Понятие field отсутсвует на уровне языка● Есть только property
Properties● Понятие field отсутсвует на уровне языка● Есть только property● Пара setter + getter для var и getter для val
Properties● Понятие field отсутсвует на уровне языка● Есть только property● Пара setter + getter для var и getter для val● Все обращения происходят через getter и setter
Properties● Понятие field отсутсвует на уровне языка● Есть только property● Пара setter + getter для var и getter для val● Все обращения происходят через getter и setter● При необходимости у property есть backing field
Property Delegation
Property Delegationclass HelloDelegates { var property by Delegate()}
Property Delegationclass HelloDelegates { var property by Delegate()}
class HelloDelegates { private val delegate = Delegate()}
Property Delegationclass HelloDelegates { var property by Delegate()}
class HelloDelegates { private val delegate = Delegate()
var property: String set(value) = delegate.setValue(this, metadata, value) get() = delegate.getValue(this, metadata)}
Property Delegationclass Delegate { operator fun getValue(thisRef: Any, property: KProperty<*>): String operator fun setValue(thisRef: Any, property: KProperty<*>, value: String)}
Property Delegationclass Delegate { operator fun getValue(thisRef: Any, property: KProperty<*>): String operator fun setValue(thisRef: Any, property: KProperty<*>, value: String)}
class Delegate : ReadOnlyProperty<Any, String>class Delegate : ReadWriteProperty<Any, String>
Built-in delegates● Delegates.notNull()● lazy● Delegates.observable()● Delegates.vetoable
Delegates.notNull()class ChatFragment : Fragment() { override fun onAttach(activity: Activity) { super.onAttach(activity) adapter = createMessagesAdapter(context) }}
Delegates.notNull()class ChatFragment : Fragment() { private var adapter: MessagesAdapter? = null
override fun onAttach(activity: Activity) { super.onAttach(activity) adapter = createMessagesAdapter(context) }}
Delegates.notNull()class ChatFragment : Fragment() { private var adapter: MessagesAdapter? = null
override fun onAttach(activity: Activity) { super.onAttach(activity) adapter = createMessagesAdapter(context) }
private fun onMessagesChanged(messages: List<Message>) { adapter!!.notifyMessagedChanged(messages) }}
Delegates.notNull()class ChatFragment : Fragment() { private var adapter: MessagesAdapter by Delegates.notNull()
override fun onAttach(activity: Activity) { super.onAttach(activity) adapter = createMessagesAdapter(context) }
private fun onMessagesChanged(messages: List<Message>) { adapter!!.notifyMessagedChanged(messages) }}
Delegates.notNull()class ChatFragment : Fragment() { private var adapter: MessagesAdapter by Delegates.notNull()
override fun onAttach(activity: Activity) { super.onAttach(activity) adapter = createMessagesAdapter(context) }
private fun onMessagesChanged(messages: List<Message>) { adapter.notifyMessagedChanged(messages) }}
lazyclass ChatFragment : Fragment() { private val adapter by lazy(LazyThreadSafetyMode.NONE) { createMessagesAdapter(context) }
private fun onMessagesChanged(messages: List<Message>) { adapter.notifyMessagedChanged(messages) }}
Delegates.observableclass ChatFragment : Fragment() { private var title by Delegates.observable("Chat") { desc, old, new -> notifyTitleChanged(new) } private fun notifyTitleChanged(title: String) { titleView.text = title }}
Наш первый делегатpublic class OvalView extends View { private float radiusX = 100.0f; private float radiusY = 100.0f;}
Наш первый делегатpublic class OvalView extends View { private float radiusX = 100.0f; private float radiusY = 100.0f;
public void setRadiusX(float radiusX) { this.radiusX = radiusX; invalidate(); }}
Наш первый делегатpublic class OvalView extends View { private float radiusX = 100.0f; private float radiusY = 100.0f;
public void setRadiusX(float radiusX) { this.radiusX = radiusX; invalidate(); }
public void setRadiusY(float radiusY) { this.radiusY = radiusY; invalidate(); }}
Наш первый делегатclass OvalView : View() { val radiusX by Delegates.observable(100.0f) { desc, old, new -> invalidate() }
val radiusY by Delegates.observable(100.0f) { desc, old, new -> invalidate() }}
Наш первый делегатfun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> { return Delegates.observable(initial) { desc, old, new -> invalidate() }}
Наш первый делегатfun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> { return Delegates.observable(initial) { desc, old, new -> invalidate() }}
class OvalView : View() { val radiusX by bindViewProperty(100.0f) val radiusY by bindViewProperty(100.0f)}
Наш первый делегатfun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> { return Delegates.observable(initial) { desc, old, new -> invalidate() }}
class OvalView : View() { val radiusX by bindViewProperty(100.0f) val radiusY by bindViewProperty(100.0f)
val centerX by bindViewProperty(50.0f) val centerY by bindViewProperty(50.0f)}
Еще один простой делегатfun <V : View> Activity.bindView(id: Int): Lazy<V> { throw UnsupportedOperationException("Implement me!")}
Еще один простой делегатfun <V : View> Activity.bindView(id: Int): Lazy<V> { return lazy(LazyThreadSafetyMode.NONE) { findViewById(id) as V }}
Еще один простой делегатfun <V : View> Activity.bindView(id: Int): Lazy<V> { return lazy(LazyThreadSafetyMode.NONE) { findViewById(id) as V }}
class UserActivity : Activity() { private val image by bindView<ImageView>(R.id.user_image) private val firstName by bindView<TextView>(R.id.user_first_name) private val lastName by bindView<TextView>(R.id.user_last_name)}
Typesafe Bundle Builders
Typesafe Bundle Buildersval fragment = UserFragment()val extras = Bundle()
extras.putString("firstName", "Ivan")extras.putString("lastName", "Ivanov")extras.putInt("age", 20)
fragment.arguments = extras
Typesafe Bundle Buildersval fragment = UserFragment()val extras = Bundle()
extras.putString("firstName", "Ivan")extras.putString("lastName", "Ivanov")extras.putInt("age", 20)
fragment.arguments = extras
inline fun <reified T : Any> bindArgument(bundle: Bundle, default: T? = null): BundleDelegate { throw UnsupportedOperationException()}
Typesafe Bundle Buildersclass UserArguments(val extras: Bundle) { var firstName by bindArgument<String>(extras) var lastName by bindArgument<String>(extras) var age by bindArgument(extras, 33)}
Typesafe Bundle Buildersclass UserArguments(val extras: Bundle) { var firstName by bindArgument<String>(extras) var lastName by bindArgument<String>(extras) var age by bindArgument(extras, 33)}
val fragment = UserFragment()val arguments = UserArguments(Bundle())
arguments.firstName = "Ivan"arguments.lastName = "Ivanov"
fragment.arguments = arguments.extras
Typesafe Bundle Buildersclass UserFragment : Fragment() { private val args by lazy(LazyThreadSafetyMode.NONE) { UserArguments(arguments) }}
LifecycleAware delegatesclass ChatFragment : BaseFragment() { private var chat by Delegates.notNull<ChatModel>() private var messages by Delegates.notNull<CollectionRange<Message>>()}
LifecycleAware delegatesclass ChatFragment : BaseFragment() { private var chat by Delegates.notNull<ChatModel>() private var messages by Delegates.notNull<CollectionRange<Message>>()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) chat = acquireChatModel() messages = chat.acquireMessagesRange() }}
LifecycleAware delegatesclass ChatFragment : BaseFragment() { private var chat by Delegates.notNull<ChatModel>() private var messages by Delegates.notNull<CollectionRange<Message>>()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) chat = acquireChatModel() messages = chat.acquireMessagesRange() }
override fun onDestroy() { messages.close() chat.close() super.onDestroy() }}
LifecycleAware delegatesclass ChatFragment : BaseFragment() { private val chat by bindToLifecycleEagerly(LifecycleInterval.CREATED) { acquireChatModel() }
private val participants by bindToLifecycleEagerly(LifecycleInterval.CREATED) { chat.acquireParticipantsRange() }}
Preferences delegatesclass DebugSettings(private val preferences: SharedPreferences) { var logEnabled: Boolean set(value) = preferences.edit().putBoolean("logEnabled", value).apply() get() = preferences.getBoolean("logEnabled", true)
var logTag: String set(value) = preferences.edit().putString("logTag", value).apply() get() = preferences.getString("logTag", "Debug")}
Preferences delegatesclass DebugSettings(private val preferences: SharedPreferences) { var logEnabled by bindPreference(preferences, true) var logTag by bindPreference(preferences, "Debug")}
ReadWriteProperty и ReadOnlyProperty
ReadWriteProperty и ReadOnlyPropertypublic interface ReadWriteProperty<in R, T> { public operator fun getValue(thisRef: R, property: KProperty<*>): T public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)}
public interface ReadOnlyProperty<in R, out T> { public operator fun getValue(thisRef: R, property: KProperty<*>): T}
KProperty<T>
KProperty<T>● Хранит в себе метаинформацию о property
KProperty<T>● Хранит в себе метаинформацию о property● KProperty<T>.name: String
KProperty<T>● Хранит в себе метаинформацию о property● KProperty<T>.name: String● KProperty<T>.returnType.javaType: Class
KProperty<T>● Хранит в себе метаинформацию о property● KProperty<T>.name: String● KProperty<T>.returnType.javaType: Class● KProperty<T>.returnType.isMarkedNullable: Boolean
KProperty<T>● Хранит в себе метаинформацию о property● KProperty<T>.name: String● KProperty<T>.returnType.javaType: Class● KProperty<T>.returnType.isMarkedNullable: Boolean● KProperty<T>.annotations: List<Annotation>
KProperty<T> и Android
KProperty<T> и Android● По дефолту работает только KProperty.name
KProperty<T> и Android● По дефолту работает только KProperty.name● Оставшимся методам необходима зависимость kotlin-reflect
KProperty<T> и Android● По дефолту работает только KProperty.name● Оставшимся методам необходима зависимость kotlin-reflect● Количество методов - 12112
KProperty<T> и Android● По дефолту работает только KProperty.name● Оставшимся методам необходима зависимость kotlin-reflect● Количество методов - 12112● JAR Size - 2268KB
KProperty<T> и Android● По дефолту работает только KProperty.name● Оставшимся методам необходима зависимость kotlin-reflect● Количество методов - 12112● JAR Size - 2268KB● DEX size - 1726KB
Как жить без reflection
Как жить без reflection● KProperty.name работает и так
Как жить без reflection● KProperty.name работает и так● KProperty.returnType.javaType - inline reified generic функция
Как жить без reflection● KProperty.name работает и так● KProperty.returnType.javaType - inline reified generic функция● KProperty.returnType.isMarkedNullable - два разных делегата
Как жить без reflection● KProperty.name работает и так● KProperty.returnType.javaType - inline reified generic функция● KProperty.returnType.isMarkedNullable - два разных делегата
inline fun <reified T : Any> bindValue(): ReadWriteProperty<Any, T> { return createRequiredDelegate(T::class.java)}
Как жить без reflection● KProperty.name работает и так● KProperty.returnType.javaType - inline reified generic функция● KProperty.returnType.isMarkedNullable - два разных делегата
inline fun <reified T : Any> bindValue(): ReadWriteProperty<Any, T> { return createRequiredDelegate(T::class.java)}
inline fun <reified T : Any> bindOptionalValue(): ReadWriteProperty<Any, T?> { return createOptionalDelegate(T::class.java)}
Preferences delegatesclass DebugSettings(private val preferences: SharedPreferences) { var logEnabled by bindPreference(preferences, true) var logTag by bindPreference(preferences, "Debug")}
Preferences delegatesclass PreferenceReadWriteProperty<T : Any>( // some arguments here) : ReadWriteProperty<Any, T> { // implement me}
Preferences delegatesclass PreferenceReadWriteProperty<T : Any>( private val preferences: SharedPreferences) : ReadWriteProperty<Any, T> { // implement me}
Preferences delegatesclass PreferenceReadWriteProperty<T : Any>( private val preferences: SharedPreferences, private val clazz: Class<T>) : ReadWriteProperty<Any, T> { // implement me}
Preferences delegatesclass PreferenceReadWriteProperty<T : Any>( private val preferences: SharedPreferences, private val clazz: Class<T>, private val default: T) : ReadWriteProperty<Any, T> { // implement me}
Preferences delegatesclass PreferenceReadWriteProperty<T : Any>( private val preferences: SharedPreferences, private val clazz: Class<T>, private val default: T, private val name: String?) : ReadWriteProperty<Any, T> { // implement me}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { throw UnsupportedOperationException()}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default }}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default } return when (clazz) { String::class.java -> preferences.getString(key, null) else -> throw UnsupportedOperationException() } as T}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default } return when (clazz) { String::class.java -> preferences.getString(key, null) Int::class.java -> preferences.getInt(key, 0) else -> throw UnsupportedOperationException() } as T}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default } return when (clazz) { String::class.java -> preferences.getString(key, null) Int::class.java -> preferences.getInt(key, 0) Long::class.java -> preferences.getLong(key, 0L) else -> throw UnsupportedOperationException() } as T}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default } return when (clazz) { String::class.java -> preferences.getString(key, null) Int::class.java -> preferences.getInt(key, 0) Long::class.java -> preferences.getLong(key, 0L) Boolean::class.java -> preferences.getBoolean(key, false) else -> throw UnsupportedOperationException() } as T}
Preferences delegatesoverride fun getValue(thisRef: Any, property: KProperty<*>): T { val key = name ?: property.name if (!preferences.contains(key)) { return default } return when (clazz) { String::class.java -> preferences.getString(key, null) Int::class.java -> preferences.getInt(key, 0) Long::class.java -> preferences.getLong(key, 0L) Boolean::class.java -> preferences.getBoolean(key, false) Float::class.java -> preferences.getFloat(key, 0.0f) else -> throw UnsupportedOperationException() } as T}
Preferences delegatesinline fun <reified T : Any> bindPreference( preferences: SharedPreferences, default: T, name: String? = null) : ReadWriteProperty<Any, T> { return PreferenceReadWriteProperty(preferences, T::class.java, default, name)}
Preferences delegatesclass DebugSettings(private val preferences: SharedPreferences) { var logEnabled by bindPreference(preferences, true) var logTag by bindPreference(preferences, "Debug")}
Linkshttps://kotlinlang.org/docs/reference/delegated-properties.htmlhttps://github.com/JakeWharton/kotterknifehttps://github.com/nsk-mironov/kotlin-jetpack