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

Delegation in Kotlin

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.

Ralf

July 18, 2019
Tweet

More Decks by Ralf

Other Decks in Technology

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