Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Delegates: Reduce the boilerplate

Kotlin Delegates: Reduce the boilerplate

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.

Dmytro Zaitsev

December 12, 2017
Tweet

More Decks by Dmytro Zaitsev

Other Decks in Programming

Transcript

  1. External Can be implemented in any object-oriented language Two types

    of Delegation Internal Requires language support
  2. External Delegation class Rectangle(val width: Int, val height: Int) :

    Area { override fun area(): Int = width * height }
  3. External Delegation class Window(val rect: Rectangle) : Area { //

    Delegation override fun area(): Int = rect.area() }
  4. 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(); } }
  5. When I should care? When you want to extend the

    behavior of classes that you cannot or don’t want to subclass because…
  6. 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
  7. 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>
  8. 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
  9. 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) }
  10. 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!" } }
  11. 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.'") } }
  12. This is generated by the compiler instead public 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); } }
  13. observable class Person { var name by Delegates.observable("Dmytro") { prop:

    KProperty<*>, old, new -> println("$old -> $new") } }
  14. Vetoable class Person { var name by Delegates.vetoable("Dmytro") { prop:

    KProperty<*>, old, new -> new.startsWith("D") } }
  15. Vetoable class Person { var name by Delegates.vetoable("Dmytro") { prop:

    KProperty<*>, old, new -> new.startsWith("D") } }
  16. 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 }
  17. Vanilla Android class PersonActivity : Activity() { private lateinit var

    firstName: TextView private var lastName: TextView by Delegates.notNull() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) firstName = findViewById(R.id.first_name) as TextView lastName = findViewById(R.id.last_name) as TextView } }
  18. Butterknife/Kotterknife class PersonActivity : Activity() { private val firstName: TextView

    by bindView(R.id.first_name) private val lastName: TextView by bindView(R.id.last_name) }
  19. Butterknife/Kotterknife fun <V : View> Activity.bindView(id: Int): ReadOnlyProperty<Activity, V> {

    return object : ReadOnlyProperty<View, V> { private var value: Any? = EMPTY override fun getValue(thisRef: Activity, property: KProperty<*>): V { if (value == EMPTY) { value = thisRef.findViewById(id) as V? ?: throw IllegalStateException() } return value as V } } }
  20. For example ×Build type-safe APIs on top of unsafe APIs:

    SharedPreferences, Bundle, Intent, Cursor
 ×DI without annotation processing: KOIN, injekt etc