Slide 1

Slide 1 text

Delegation in Kotlin Ralf Wondratschek @vRallev

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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 }

Slide 9

Slide 9 text

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 }

Slide 10

Slide 10 text

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 }

Slide 11

Slide 11 text

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 }

Slide 12

Slide 12 text

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 }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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 }

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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.

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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 }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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 }

Slide 28

Slide 28 text

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 }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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 }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Thank you! Ralf Wondratschek @vRallev