Slides from my talk at Kyiv Kotlin User Group: First Blood, 2017-12-12
Video: https://youtu.be/s2m1w8t7k3Y?t=5730
The Delegation pattern is an OOD pattern that allows object composition to achieve the same code reuse as inheritance.
Let's find out what Kotlin offers us in a world where we have no control over the creation of many objects to save our time in many cases.
13. Translation rule
public final class Window implements Area {
// $FF: synthetic field
private final Rectangle $$delegate_0 = new Rectangle(10, 10);
public int area() {
return this.$$delegate_0.area();
}
}
16. When I should care?
When you want to extend the
behavior of classes that you cannot
or don’t want to subclass because…
17. Encapsulation, thou art a heartless bitch
×Classes are final (e.g. File)
×You offer a limited API, using 98% of the existing code
×You want to hide the implementation from calling
code, so clients cannot cast to the superclass
19. What properties would we like to have?
×Lazy (computed only upon first access)
×Observable (listeners get notified about changes)
×Storing in a map, not in separate field each
×<Whatever I want property semantics>
21. Val / Var: define an operator
// For val
operator fun getValue(thisRef: R, prop: KProperty<*>): T
// For var
operator fun getValue(thisRef: R, prop: KProperty<*>): T
operator fun setValue(thisRef: R, prop: KProperty<*>, value: T)
Property
metadata
Property owner
22. Val / Var: or implement an interface
// For val
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, prop: KProperty<*>): T
}
// For var
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, prop: KProperty<*>): T
operator fun setValue(thisRef: R, prop: KProperty<*>, value: T)
}
23. Read Only Properties
class Person {
val name: String by NameVal()
}
class NameVal: ReadOnlyProperty<Person, String> {
override fun getValue(thisRef: Person, prop: KProperty<*>): String {
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
}
24. Read-Write Properties
class Person {
var name: String by NameVar()
}
class NameVar: ReadWriteProperty<Person, String> {
override fun getValue(…): { … }
override fun setValue(thisRef: Person, prop: KProperty<*>, value: String) {
println("$value has been assigned to '${prop.name} in $thisRef.'")
}
}
25. This is generated by the compiler insteadpublic final class Person {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{ /* Nightmare */ }
@NotNull private final NameVar name$delegate = new NameVar();
@NotNull public final String getName() {
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
}
}
33. Map
class Person(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
class PersonWrapper(map: Map<String, Any?>) {
val person: Person by map
}
40. For example
×Build type-safe APIs on top of unsafe APIs:
SharedPreferences, Bundle, Intent, Cursor
×DI without annotation processing: KOIN,
injekt etc