$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

  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++.

    View Slide

  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.

    View Slide

  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();
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  6. Delegation in Kotlin
    Kotlin made delegation first class citizen like other design patterns,
    e.g. Singleton.
    Supports two types of delegation.

    View Slide

  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

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  13. Proxy class
    interface Shape {
    fun area(): Int
    }
    class Unimplemented : Shape {
    override fun area(): Int {
    TODO("not implemented")
    }
    }

    View Slide

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

    View Slide

  15. Proxy class
    interface Shape {
    fun area(): Int
    }
    class Unimplemented : Shape {
    override fun area(): Int {
    TODO("not implemented")
    }
    }
    class Unimplemented : Shape by TODO()

    View Slide

  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.

    View Slide

  17. But why?
    interface Lazy {
    fun get(): T
    }
    class Square(side: Int) : Shape {
    val lazyRectangle = object : Lazy {
    override fun get(): Rectangle = Rectangle(side, side)
    }
    override fun area(): Int = lazyRectangle.get().area()
    }

    View Slide

  18. Lazy
    class Square(side: Int) : Shape {
    val rectangle by lazy { Rectangle(side, side) }
    override fun area(): Int = rectangle.area()
    }

    View Slide

  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
    }

    View Slide

  20. Interfaces
    // Use for val
    interface ReadOnlyProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    }
    // Use for var
    interface ReadWriteProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    }

    View Slide

  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)
    }
    }

    View Slide

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

    View Slide

  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)
    }
    }

    View Slide

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

    View Slide

  25. KProperty
    Represents a property in Kotlin’s reflection API.
    Part of the standard library.
    Provides useful information like name, visibility and other attributes

    View Slide

  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()
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. Saving instance state
    private sealed class BaseInstanceStateProperty(
    private val defaultValue: T
    ) : ReadWriteProperty, 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
    }
    }

    View Slide

  31. Saving instance state
    private sealed class BaseInstanceStateProperty(
    private val defaultValue: T
    ) : ReadWriteProperty, 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) }
    }
    }

    View Slide

  32. Saving instance state
    private sealed class BaseInstanceStateProperty(
    private val defaultValue: T
    ) : ReadWriteProperty, 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
    }
    }
    }

    View Slide

  33. Saving instance state
    private class IntInstanceStateProperty(
    defaultValue: Int
    ) : BaseInstanceStateProperty(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)
    }
    }

    View Slide

  34. Saving instance state
    private class StringInstanceStateProperty(
    defaultValue: String
    ) : BaseInstanceStateProperty(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)
    }
    }

    View Slide

  35. Saving instance state
    fun instanceState(defaultValue: T): ReadWriteProperty {
    return when (defaultValue) {
    is Int -> IntInstanceStateProperty(defaultValue)
    is String -> StringInstanceStateProperty(defaultValue)
    else -> throw NotImplementedError("Missing implementation...")
    } as ReadWriteProperty
    }

    View Slide

  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()
    }
    }

    View Slide

  37. Saving instance state
    class MainActivity : AppCompatActivity() {
    private var clickCount by instanceState(defaultValue = 0)
    fun click(view: View) {
    clickCount++
    updateButtonText()
    }
    }

    View Slide

  38. Operator
    // Use for val
    interface ReadOnlyProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    }
    // Use for var
    interface ReadWriteProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    }

    View Slide

  39. Operator
    class User(val map: Map) {
    val name: String by map
    val age: Int by map
    }
    val user = User(mapOf(
    "name" to "John Doe",
    "age" to 25
    ))

    View Slide

  40. Operator
    operator fun Map.getValue(
    thisRef: Any?,
    property: KProperty<*>
    ): V1 = @Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)

    View Slide

  41. Operator
    inline operator fun 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

    View Slide

  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
    ))

    View Slide

  43. Source code
    https://github.com/vRallev/delegate-meetup

    View Slide

  44. Summary
    Kotlin supports true object oriented delegation.
    Two types: implementation by delegation and property delegation.
    Two very powerful mechanisms in our Kotlin toolbox.

    View Slide

  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

    View Slide

  46. Thank you!
    Ralf Wondratschek
    @vRallev

    View Slide