Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Delegation in Kotlin

7cfefc4ecbffbe84b59de233d3fa4645?s=47 Ralf
July 18, 2019

Delegation in Kotlin

Kotlin supports two types of delegation: Implementation by delegation and property delegation. This talk explores what the delegation pattern is and means in object oriented programming and how the programming language Kotlin supports it.

7cfefc4ecbffbe84b59de233d3fa4645?s=128

Ralf

July 18, 2019
Tweet

Transcript

  1. Delegation in Kotlin Ralf Wondratschek @vRallev

  2. Delegation Delegation is a design pattern … and often misunderstood.

    Not part of the Gang of Four design pattern book. Many languages don’t support object oriented delegation, e.g. Java and C++.
  3. Delegation is powerful One tool in a box with many

    other structural design patterns like the proxy, adapter or composition patterns. Plays nicely with dependency injection. Helps breaking down big classes.
  4. Delegation pattern public class Rectangle { private final int width;

    private final int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } public int area() { return width * height; } } https://en.wikipedia.org/wiki/Delegation_pattern public class Square { private final Rectangle rectangle; public Square(int side) { this.rectangle = new Rectangle(side, side); } public int area() { return rectangle.area(); } }
  5. Delegation public class Area { public int area(Square square) {

    return square.side * square.side; } } https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming) public static class Square { private final int side; private final Area area; public Square(int side) { this.side = side; this.area = new Area(); } public int area() { return area.area(this); } }
  6. Delegation in Kotlin Kotlin made delegation first class citizen like

    other design patterns, e.g. Singleton. Supports two types of delegation.
  7. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) { fun area(): Int = width * height } class Square(side: Int) { private val rectangle = Rectangle(side, side) fun area(): Int = rectangle.area() } https://kotlinlang.org/docs/reference/delegation.html
  8. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) { fun area(): Int = width * height } class Square(side: Int) { private val rectangle = Rectangle(side, side) fun area(): Int = rectangle.area() } interface Shape { fun area(): Int }
  9. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) : Shape { override fun area(): Int = width * height } class Square(side: Int) { private val rectangle = Rectangle(side, side) fun area(): Int = rectangle.area() } interface Shape { fun area(): Int }
  10. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) : Shape { override fun area(): Int = width * height } class Square(side: Int) : Shape { private val rectangle = Rectangle(side, side) override fun area(): Int = rectangle.area() } interface Shape { fun area(): Int }
  11. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) : Shape { override fun area(): Int = width * height } class Square(side: Int) : Shape by Rectangle(side, side) interface Shape { fun area(): Int }
  12. Implementation by delegation class Rectangle( private val width: Int, private

    val height: Int ) : Shape { override fun area(): Int = width * height } class Square(side: Int) : Shape by Rectangle(side, side) { override fun area(): Int = 0 } interface Shape { fun area(): Int }
  13. Proxy class interface Shape { fun area(): Int } class

    Unimplemented : Shape { override fun area(): Int { TODO("not implemented") } }
  14. Proxy class inline fun <reified T : Any> TODO(): T

    { val clazz = T::class.java return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { _, method, _ -> kotlin.TODO("Method ${method.name}() not implemented") } as T }
  15. Proxy class interface Shape { fun area(): Int } class

    Unimplemented : Shape { override fun area(): Int { TODO("not implemented") } } class Unimplemented : Shape by TODO()
  16. Property delegation https://kotlinlang.org/docs/reference/delegated-properties.html Object oriented delegation. You hide implementation details

    and delegate the knowledge to another object. Built-in delegates.
  17. But why? interface Lazy<T> { fun get(): T } class

    Square(side: Int) : Shape { val lazyRectangle = object : Lazy<Rectangle> { override fun get(): Rectangle = Rectangle(side, side) } override fun area(): Int = lazyRectangle.get().area() }
  18. Lazy class Square(side: Int) : Shape { val rectangle by

    lazy { Rectangle(side, side) } override fun area(): Int = rectangle.area() }
  19. Observable class Square(side: Int) : Shape { var side by

    Delegates.observable(side) { property, oldValue, newValue -> println("Side changed from $oldValue to $newValue") } override fun area(): Int = side * side }
  20. Interfaces // Use for val interface ReadOnlyProperty<in R, out T>

    { operator fun getValue(thisRef: R, property: KProperty<*>): T } // Use for var interface ReadWriteProperty<in R, T> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
  21. View delegate class MainActivity : AppCompatActivity() { private lateinit var

    button: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button = findViewById(R.id.button) } }
  22. View delegate private class ViewDelegate<T : View>( @IdRes private val

    viewId: Int ) : ReadOnlyProperty<Activity, T> { private var view: T? = null override fun getValue(thisRef: Activity, property: KProperty<*>): T { return view ?: thisRef.findViewById<T>(viewId).also { view = it } } } fun <T : View> view( @IdRes viewId: Int ): ReadOnlyProperty<Activity, T> = ViewDelegate(viewId)
  23. View delegate class MainActivity : AppCompatActivity() { private lateinit var

    button: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button = findViewById(R.id.button) } }
  24. View delegate class MainActivity : AppCompatActivity() { private val button

    by view<Button>(R.id.button) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  25. KProperty Represents a property in Kotlin’s reflection API. Part of

    the standard library. Provides useful information like name, visibility and other attributes
  26. Saving instance state private const val COUNT_KEY = "count_key" class

    MainActivity : AppCompatActivity() { private var clickCount = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) clickCount = savedInstanceState?.getInt(COUNT_KEY, clickCount) ?: clickCount } override fun onSaveInstanceState(outState: Bundle) { outState.putInt(COUNT_KEY, clickCount) } fun click(view: View) { clickCount++ updateButtonText() } }
  27. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { private var value = defaultValue private var key: String? = null private var savedInstanceState: Bundle? = null }
  28. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { private var key: String? = null private fun KProperty<*>.toKey(): String = this.name }
  29. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { abstract fun fromBundle(bundle: Bundle, key: String, defaultValue: T): T abstract fun saveInBundle(bundle: Bundle, key: String, value: T) }
  30. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { private var value = defaultValue final override fun getValue(thisRef: Activity, property: KProperty<*>): T { initializeValue(thisRef, property) return value } final override fun setValue(thisRef: Activity, property: KProperty<*>, value: T) { initializeValue(thisRef, property) this.value = value } }
  31. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { final override fun restore(savedInstanceState: Bundle) { key?.let { key -> if (savedInstanceState.containsKey(key) && savedInstanceState.getInt(key) != value && value != defaultValue) { throw IllegalStateException("The value ...") } } this.savedInstanceState = savedInstanceState } final override fun save(outState: Bundle) { key?.let { saveInBundle(outState, it, value) } } }
  32. Saving instance state private sealed class BaseInstanceStateProperty<T : Any>( private

    val defaultValue: T ) : ReadWriteProperty<Activity, T>, InstanceStateProperty { private var value = defaultValue private var key: String? = null private var savedInstanceState: Bundle? = null private fun initializeValue(thisRef: Activity, property: KProperty<*>) { key = key ?: property.toKey() InstanceStateController.register(thisRef, this) val bundle = savedInstanceState if (bundle != null) { value = fromBundle(bundle, key!!, defaultValue) savedInstanceState = null } } }
  33. Saving instance state private class IntInstanceStateProperty( defaultValue: Int ) :

    BaseInstanceStateProperty<Int>(defaultValue) { override fun fromBundle(bundle: Bundle, key: String, defaultValue: Int): Int = bundle.getInt(key, defaultValue) override fun saveInBundle(bundle: Bundle, key: String, value: Int) { bundle.putInt(key, value) } }
  34. Saving instance state private class StringInstanceStateProperty( defaultValue: String ) :

    BaseInstanceStateProperty<String>(defaultValue) { override fun fromBundle(bundle: Bundle, key: String, defaultValue: String): String = bundle.getString(key, defaultValue) override fun saveInBundle(bundle: Bundle, key: String, value: String) { bundle.putString(key, value) } }
  35. Saving instance state fun <T : Any> instanceState(defaultValue: T): ReadWriteProperty<Activity,

    T> { return when (defaultValue) { is Int -> IntInstanceStateProperty(defaultValue) is String -> StringInstanceStateProperty(defaultValue) else -> throw NotImplementedError("Missing implementation...") } as ReadWriteProperty<Activity, T> }
  36. Saving instance state private const val COUNT_KEY = "count_key" class

    MainActivity : AppCompatActivity() { private var clickCount = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) clickCount = savedInstanceState?.getInt(COUNT_KEY, clickCount) ?: clickCount } override fun onSaveInstanceState(outState: Bundle) { outState.putInt(COUNT_KEY, clickCount) } fun click(view: View) { clickCount++ updateButtonText() } }
  37. Saving instance state class MainActivity : AppCompatActivity() { private var

    clickCount by instanceState(defaultValue = 0) fun click(view: View) { clickCount++ updateButtonText() } }
  38. Operator // Use for val interface ReadOnlyProperty<in R, out T>

    { operator fun getValue(thisRef: R, property: KProperty<*>): T } // Use for var interface ReadWriteProperty<in R, T> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
  39. Operator class User(val map: Map<String, Any?>) { val name: String

    by map val age: Int by map } val user = User(mapOf( "name" to "John Doe", "age" to 25 ))
  40. Operator operator fun <V, V1 : V> Map<in String, V>.getValue(

    thisRef: Any?, property: KProperty<*> ): V1 = @Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
  41. Operator inline operator fun <reified V> Bundle.getValue( thisRef: Any?, property:

    KProperty<*> ): V = when (V::class) { String::class -> this.getString(property.name) Int::class -> this.getInt(property.name) else -> throw NotImplementedError("Missing for ${V::class}") } as V
  42. Operator class User(val bundle: Bundle) { val name: String by

    bundle val age: Int by bundle } val user = User(bundleOf( "name" to "John Doe", "age" to 25 ))
  43. Source code https://github.com/vRallev/delegate-meetup

  44. Summary Kotlin supports true object oriented delegation. Two types: implementation

    by delegation and property delegation. Two very powerful mechanisms in our Kotlin toolbox.
  45. Resources Shoulders of giants: Languages Kotlin learnt from (Andrey Breslav)

    https://2018.geekout.ee/andrey-breslav/ Delegation pattern https://en.wikipedia.org/wiki/Delegation_pattern https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming) Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems (Henry Lieberman) http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html Documentation https://kotlinlang.org/docs/reference/delegation.html https://kotlinlang.org/docs/reference/delegated-properties.html
  46. Thank you! Ralf Wondratschek @vRallev