Slide 1

Slide 1 text

Kotlin Delegation Philippe Breault @pbreault

Slide 2

Slide 2 text

The Speaker • Philippe Breault • Android since 2010 • Sr. Software Engineer @ American Express

Slide 3

Slide 3 text

What is Delegation In software engineering, the delegation pattern is an object-oriented design pattern that allows object composition to achieve the same code reuse as inheritance. — Wikipedia https://en.wikipedia.org/wiki/Delegation_pattern

Slide 4

Slide 4 text

What is Delegation Ask someone else to do the work for you — Phil

Slide 5

Slide 5 text

What is Delegation https://en.wikipedia.org/wiki/Delegation_pattern class Rectangle(val width: Int, val height: Int) { fun area() = width * height } class Window(val bounds: Rectangle) { // Delegation fun area() = bounds.area() }

Slide 6

Slide 6 text

Class Delegation

Slide 7

Slide 7 text

Class Delegation Code sharing without inheritance ✴ So let’s start with inheritance !

Slide 8

Slide 8 text

Square Rectangle area = width * height Height Width Width == Height Width != Height Side

Slide 9

Slide 9 text

Inheritance Square Rectangle Shape + area() : Int

Slide 10

Slide 10 text

Inheritance Square Rectangle Shape + area() : Int

Slide 11

Slide 11 text

Inheritance interface Shape { fun area(): Float } open class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Rectangle(side, side)

Slide 12

Slide 12 text

Inheritance interface Shape { fun area(): Float } open class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Rectangle(side, side)

Slide 13

Slide 13 text

Inheritance interface Shape { fun area(): Float } open class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Rectangle(side, side)

Slide 14

Slide 14 text

Inheritance - Problems? open class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Rectangle(side, side) Square + side : Int + height : Int + width : Int Is this the api that we want? Redundant properties? Rectangle can be extended by any class

Slide 15

Slide 15 text

Inheritance - Quiz? fun main() { println(Square().toName()) } fun Shape.toName() = when (this) { is Rectangle -> "Rectangle" is Square -> "Square" else -> "?" } open class Rectangle(…) : Shape class Square(…) : Rectangle(…) Will the answer be A. “Square” B. “Rectangle” C. “Rectangle” and “Square” D. “?”

Slide 16

Slide 16 text

Inheritance - Quiz? fun main() { println(Square().toName()) } fun Shape.toName() = when (this) { is Rectangle -> "Rectangle" is Square -> "Square" else -> "?" } open class Rectangle(…) : Shape class Square(…) : Rectangle(…) Will the answer be A. “Rectangle” B. “Square” C. “Rectangle” and “Square” D. “?”

Slide 17

Slide 17 text

Class Delegation interface Shape { fun area(): Float } open class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Rectangle(side, side)

Slide 18

Slide 18 text

Class Delegation interface Shape { fun area(): Float } class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Shape by Rectangle(side, side)

Slide 19

Slide 19 text

Class Delegation interface Shape { fun area(): Float } class Rectangle(val width: Float, val height: Float) : Shape { override fun area() = width * height } class Square(val side: Float) : Shape by Rectangle(side, side)

Slide 20

Slide 20 text

Class Delegation class Square(val side: Float) : Shape by Rectangle(side, side) // Is the same as: class Square(val side: Float) : Shape { private val delegate = Rectangle(side, side) override fun area() = delegate.area() }

Slide 21

Slide 21 text

Class Delegation public final class Square implements Shape { private final float side; // $FF: synthetic field private final Rectangle $$delegate_0; public final float getSide() { return this.side; } public Square(float side) { this.$$delegate_0 = new Rectangle(side, side); this.side = side; } public float area() { return this.$$delegate_0.area(); } }

Slide 22

Slide 22 text

class MyLoggingList(val delegate: MutableList = mutableListOf()) : MutableList by delegate { override fun add(element: T): Boolean { println("Adding $element") return delegate.add(element) } // override fun add(index: Int, element: T) {...} // override fun addAll(index: Int, elements: Collection): Boolean {...} // override fun addAll(elements: Collection): Boolean {...} }

Slide 23

Slide 23 text

class MyLoggingList(val delegate: MutableList = mutableListOf()) : MutableList by delegate { override fun add(element: T): Boolean { println("Adding $element") return delegate.add(element) } // override fun add(index: Int, element: T) {...} // override fun addAll(index: Int, elements: Collection): Boolean {...} // override fun addAll(elements: Collection): Boolean {...} }

Slide 24

Slide 24 text

Recap - Class Delegates • Compile time operation • Hides complexity • Keeps classes final The Good The Bad The Ugly • Can’t change the delegate after it being set up • Delegates can’t interact with overridden methods • Overriding a method can break the internal state of the delegate

Slide 25

Slide 25 text

Property Delegation

Slide 26

Slide 26 text

What is Property Delegation • Replacement for get()/set() var age: Int = 100 set(value) { if (value > 0) field = value } var age: Int by vetoable(100) { _, _, newValue -> newValue > 0 } • Shareable logic using the “by” keyword

Slide 27

Slide 27 text

Lazy val lazyDelegate by lazy { computeSomethingExpensive() }

Slide 28

Slide 28 text

Observable var spied by observable(10) { property, oldValue, newValue -> println("changed from $oldValue to $newValue") }

Slide 29

Slide 29 text

Vetoable var age by vetoable(initialValue = 10) { _, _, newValue -> newValue > 0 }

Slide 30

Slide 30 text

NotNull var someInt by notNull()

Slide 31

Slide 31 text

Custom Property Delegates

Slide 32

Slide 32 text

Custom Delegates • ReadOnlyProperty ( val ) • ReadWriteProperty ( var ) • ObservableProperty ( var)

Slide 33

Slide 33 text

ReadOnlyProperty // Declaration class LogOnRead(private val initialValue: T) : ReadOnlyProperty { override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $initialValue") return initialValue } } // Usage class MyClass { val w by LogOnRead(initialValue = 10) //... }

Slide 34

Slide 34 text

ReadOnlyProperty // Declaration class LogOnRead(private val initialValue: T) : ReadOnlyProperty { override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $initialValue") return initialValue } } // Usage class MyClass { val w by LogOnRead(initialValue = 10) //... }

Slide 35

Slide 35 text

ReadOnlyProperty // Declaration class LogOnRead(private val initialValue: T) : ReadOnlyProperty { override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $initialValue") return initialValue } } // Usage class MyClass { val w by LogOnRead(initialValue = 10) //... }

Slide 36

Slide 36 text

ReadOnlyProperty // Declaration class LogOnRead(private val initialValue: T) : ReadOnlyProperty { override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $initialValue") return initialValue } } // Usage class MyClass { val w by LogOnRead(initialValue = 10) //... }

Slide 37

Slide 37 text

ReadOnlyProperty - Generated Code public final class LogOnRead implements ReadOnlyProperty { private final Object initialValue; public Object getValue(@NotNull Object thisRef, @NotNull KProperty property) { Intrinsics.checkParameterIsNotNull(thisRef, "thisRef"); Intrinsics.checkParameterIsNotNull(property, "property"); System.out.println("accessing " + this.initialValue); return this.initialValue; } public LogOnRead(Object initialValue) { this.initialValue = initialValue; } }

Slide 38

Slide 38 text

ReadOnlyProperty - Generated Code // Kotlin val myProp by LogOnRead(initialValue = “Hello”) // Java @NotNull private final LogOnRead myProp$delegate = new LogOnRead("Hello"); @NotNull public final String getMyProp() { return (String)this.myProp$delegate.getValue(this, $$delegatedProperties[0]); }

Slide 39

Slide 39 text

ReadOnlyProperty - Generated Code // Java @NotNull private final LogOnRead myProp$delegate = new LogOnRead("Hello"); @NotNull public final String getMyProp() { return (String)this.myProp$delegate.getValue(this, $$delegatedProperties[0]); } // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{ (KProperty) Reflection.property1(new PropertyReference1Impl( Reflection.getOrCreateKotlinClass(MyClass.class), "myProp", "getMyProp()I" )) };

Slide 40

Slide 40 text

ReadWriteProperty class LogOnReadWrite(initialValue: T) : ReadWriteProperty { private var value = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $value") return value } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("changing ${this.value} to $value ") this.value = value } }

Slide 41

Slide 41 text

ReadWriteProperty class LogOnReadWrite(initialValue: T) : ReadWriteProperty { private var value = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $value") return value } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("changing ${this.value} to $value ") this.value = value } }

Slide 42

Slide 42 text

ReadWriteProperty class LogOnReadWrite(initialValue: T) : ReadWriteProperty { private var value = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $value") return value } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("changing ${this.value} to $value ") this.value = value } }

Slide 43

Slide 43 text

ReadWriteProperty class LogOnReadWrite(initialValue: T) : ReadWriteProperty { private var value = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $value") return value } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("changing ${this.value} to $value ") this.value = value } }

Slide 44

Slide 44 text

ReadWriteProperty class LogOnReadWrite(initialValue: T) : ReadWriteProperty { private var value = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("accessing $value") return value } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("changing ${this.value} to $value ") this.value = value } }

Slide 45

Slide 45 text

ObservableProperty class LogOnWrite(initialValue: T) : ObservableProperty(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) { println("Changed from $oldValue to $newValue") } override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T) = true // Accept change // = false // Reject change }

Slide 46

Slide 46 text

ObservableProperty class LogOnWrite(initialValue: T) : ObservableProperty(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) { println("Changed from $oldValue to $newValue") } override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T) = true // Accept change // = false // Reject change }

Slide 47

Slide 47 text

ObservableProperty class LogOnWrite(initialValue: T) : ObservableProperty(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) { println("Changed from $oldValue to $newValue") } override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T) = true // Accept change // = false // Reject change }

Slide 48

Slide 48 text

Will the answer be val p: String? by observable("Hello") {/* ... */} fun main() { if (!p.isNullOrEmpty()) { val firstChar = p[0] } } A. “H” B. Won’t compile C. Crash

Slide 49

Slide 49 text

Will the answer be A. “H” B. Won’t compile C. Crash val p: String? by observable("Hello") {/* ... */} fun main() { if (!p.isNullOrEmpty()) { val firstChar = p[0] } }

Slide 50

Slide 50 text

Recap • Compile time operation • Hides complexity • Code reuse for properties The Good The Bad The Ugly • Doesn’t play well with smart cast • Reflection call during class loading • Breaks expectations of what “val” means • Potential side-effects on read & writes

Slide 51

Slide 51 text

Recap

Slide 52

Slide 52 text

Class Delegates • An alternative to inheritance • Compile time • Invisible to the end user (mostly) class Square(val side: Float) : Shape by Rectangle(side, side) • Reuse logic for Properties • Built-in delegates for • lazy • vetoable • observable • notNull • Can build your own • The compiler generates the underlying code Property Delegates val lazyDelegate by lazy { expensive() }

Slide 53

Slide 53 text

Should you use them? • Does it make the code cleaner? • Is it simple to understand? • Is it free of side-effects ( or are they explicit ) ? • Is it worth the additional complexity? A few questions to ask

Slide 54

Slide 54 text

Thank You Philippe Breault @pbreault