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.
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++.
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.
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(); } }
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); } }
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
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 }
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 }
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 }
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 }
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 }
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 }
Proxy class interface Shape { fun area(): Int } class Unimplemented : Shape { override fun area(): Int { TODO("not implemented") } } class Unimplemented : Shape by TODO()
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.
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() }
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 }
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) }
KProperty Represents a property in Kotlin’s reflection API. Part of the standard library. Provides useful information like name, visibility and other attributes
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 }
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 }
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) }
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 } }
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 }
Saving instance state class MainActivity : AppCompatActivity() { private var clickCount by instanceState(defaultValue = 0) fun click(view: View) { clickCount++ updateButtonText() } }
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) }
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
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 ))
Summary Kotlin supports true object oriented delegation. Two types: implementation by delegation and property delegation. Two very powerful mechanisms in our Kotlin toolbox.
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