Wouldnt it be nice to delegate repeating things and just focus on the parts that make the difference?
We all prefer composition over inheritance, but Java doesn’t help us with all the inheritance it has.
Kotlin has convenient solutions to create reusable code with delegation.
Besides the obvious benefit of delegation, there are ways to trick compiler and have better code separation.
2. Page @snnkzk
In software engineering, the delegation pattern is an object-oriented
design pattern that allows object composition to achieve the same
code reuse as an inheritance.
(https://en.wikipedia.org/wiki/Delegation_pattern)
1
4. Page
– Manually implemented logic inside get/set methods
– Code duplication
– Hard to test every usage
@snnkzk
Without Property Delegation
3
5. Page @snnkzk
Logic in get method
private var _name: String? = null
fun getName(): String {
if (_name == null) {
_name = ”Sinan"
}
return _name!!
}
4
6. Page @snnkzk
Logic in get method
private var _name: String? = null
fun getName(): String {
if (_name == null) {
_name = ”Sinan"
}
return _name!!
}
val name: String by lazy { "Sinan" }
5
7. Page @snnkzk
– Need nullable private backing field
– Code duplication for every new field
– Backing field is useable in same class
@snnkzk
Logic in get method for object creation
private var _name: String? = null
fun getName(): String {
if (_name == null) {
_name = ”Sinan"
}
return _name!!
}
6
8. Page @snnkzk
– A helper from Kotlin standard library
– An interface with a value object
– Run function when property is used
– “lazy() is a function that takes a
lambda and returns an instance
of Lazy<T> which can serve as a
delegate for implementing a lazy
property: the first call to get()
executes the lambda passed to
lazy() and remembers the result,
subsequent calls to get() simply
return the remembered result.”
What is lazy?
7
9. Page
– Language word for delegation
– For delegation property, there are two interfaces
• ReadWriteProperty
• ReadOnlyProperty
– For implementation delegation it expectes `interface by instance`
@snnkzk
What is ‘by’?
8
10. Page
– Really similar to lazy
– Creates a new view model and caches it
– Custom view model factory can be passed
@snnkzk
by viewModels() from AndroidX
9
11. Page
– Really similar to lazy
– Creates a new view model and caches it
– Custom view model factory can be passed
@snnkzk
by viewModels() from AndroidX
10
val model: MyViewModel by viewModels()
12. Page
– It is possible to create delegation depending on other types
– Instance will be passed as ref
– @AskAshDavies will write about it, maybe even a library
@snnkzk
Custom Examples
private var SharedPreferences.somePref: String by
sharedPreferencesStringOrDefault(”default value")
11
14. Page
– Delegate interface to different instance
– Prevent Code duplication
– Helps to contain logic as unit
– Compiler rewrite our classes with delegation
@snnkzk
Implementation Delegation
13
15. Page
– import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class JavaDelegatedList<T> implements List<T> {
private final List<T> innerList;
private int addCount = 0;
public JavaDelegatedList(List<T> innerList) {
this.innerList = innerList;
}
@Override
public int size() {
return innerList.size();
}
@Override
public boolean isEmpty() {
return innerList.isEmpty();
}
@Override
public boolean contains(Object o) {
return innerList.contains(o);
}
@Override
public Iterator<T> iterator() {
return innerList.iterator();
}
@snnkzk
Java Delegation
@Override
public Object[] toArray() {
return innerList.toArray();
}
@Override
public boolean add(T o) {
addCount++;
return innerList.add(o);
}
@Override
public boolean remove(Object o) {
return innerList.remove(o);
}
@Override
public boolean addAll(Collection c) {
addCount += c.size();
return innerList.addAll(c);
}
@Override
public boolean addAll(int index, Collection c) {
addCount += c.size();
return innerList.addAll(index, c);
}
@Override
public void clear() {
innerList.clear();
}
@Override
public T get(int index) {
return innerList.get(index);
}
@Override
public T set(int index, T element) {
return innerList.set(index, element);
}
@Override
public void add(int index, T element) {
addCount++;
innerList.add(index, element);
}
@Override
public T remove(int index) {
return innerList.remove(index);
}
@Override
public int indexOf(Object o) {
return innerList.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return innerList.lastIndexOf(o);
}
@Override
public ListIterator<T> listIterator() {
return innerList.listIterator();
}
@Override
public ListIterator<T> listIterator(int index) {
return innerList.listIterator(index);
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return innerList.subList(fromIndex, toIndex);
}
@Override
public boolean retainAll(Collection c) {
return false;
}
@Override
public boolean removeAll(Collection c) {
return false;
}
@Override
public boolean containsAll(Collection c) {
return false;
}
@Override
public <T> T[] toArray(T[] a) {
return innerList.toArray(a);
}
}
14
17. Page
– That is what they thought us 🤔
– We also learned why. Thanks Android
– Activities and Fragments forces us to use inheritance
@snnkzk
Favour composition over inheritance
16
18. Page
– That is what they thought us 🤔
– We also learned why. Thanks Android
– Activities and Fragments forces us to use inheritance
– Jean-Michel Fayard https://dev.to/jmfayard/android-s-billion-dollar-mistake-327b
@snnkzk
Favour composition over inheritance
17
20. Page @snnkzk
– Classes are final by default
– Data classes cannot be open
– You can inherit a data class from
a non-data class. Inheriting a
data class from another data
class is not allowed because
there is no way to make compiler-
generated data class methods
work consistently and intuitively
in case of inheritance. (Answer
from JetBrain team)
Why is Kotlin different
19
21. Page @snnkzk
interface ClosedShape {
fun area(): Int
}
class Rectangle(val width: Int, val height: Int) : ClosedShape {
override fun area() = width * height
}
class Circle(val radius: Int): ClosedShape {
override fun area(): Int = (Math.PI * radius * radius).toInt()
}
// We can have circle or rectangle window
class Window(private val bounds: ClosedShape) : ClosedShape by bounds
20
22. Page
– Need water, milk and espresso
– All have volume, name and price
– Any product needs to have invoice
@snnkzk
Coffee Shop Program Example
21
23. Page @snnkzk
Coffee Shop Program Example
data class Water(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 0.0
) : Beverage
22
24. Page @snnkzk
Coffee Shop Program Example
data class Water(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 0.0
) : Beverage
data class HotWater(override val volume: Double) :
Beverage by Water(name = "Hot water", volume = volume)
data class ColdWater(override val volume: Double) :
Beverage by Water(name = "Cold water", volume = volume)
23
25. Page @snnkzk
Coffee Shop Program Example
data class Milk(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 1
) : Beverage
24
26. Page @snnkzk
Coffee Shop Program Example
data class Milk(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 1
) : Beverage
data class HotMilk(override val volume: Double) :
Beverage by Milk(name = "Hot milk", volume = volume)
data class ColdMilk(override val volume: Double) :
Beverage by Milk(name = "Cold milk", volume = volume)
25
27. Page @snnkzk
Coffee Shop Program Example
data class Espresso(
override val volume: Double = 1.0,
private val base: Beverage = HotWater(volume)
) : Beverage by base {
override val name: String = "Espresso"
override val cost: Double = base.cost + volume * 3
}
26
28. Page
– We have base beverages water, milk and espresso
– We can create an extension function for plus operation
– End result of plus will be final product
– data class CombinedBeverage
@snnkzk
Coffee Shop Program Example
27
29. Page @snnkzk
Coffee Shop Program Example
data class Coffee(override val name: String = "Coffee") :
Beverage by Espresso() + HotWater(volume = 2.0)
data class ColdCoffee(override val name: String = "Cold coffee") :
Beverage by Espresso() + ColdWater(volume = 2.0)
data class HotMilkCoffee(override val name: String = "Hot Milk Coffee") :
Beverage by Espresso() + HotMilk(volume = 2.0)
data class ColdMilkCoffee(override val name: String = "Cold Milk Coffee") :
Beverage by Espresso() + ColdMilk(volume = 2.0)
data class DoubleMilkCoffee(override val name: String = "DoubleMilkCoffee") :
Beverage by Espresso() + HotMilk(volume = 4.0)
28
30. Page @snnkzk
– println("Espresso = ${Espresso().invoice()}")
// Espresso = 1.0 cc Hot water costs 0.0
– println("Coffee = ${Coffee().invoice()}")
// Coffee = 3.0 cc Espresso + Hot water costs 3.0
– println("ColdCoffee = ${ColdCoffee().invoice()}")
// ColdCoffee = 3.0 cc Espresso + Cold water costs 3.0
– println("HotMilkCoffee = ${HotMilkCoffee().invoice()}")
// HotMilkCoffee = 3.0 cc Espresso + Hot milk costs 5.0
– println("ColdMilkCoffee = ${ColdMilkCoffee().invoice()}")
// ColdMilkCoffee = 3.0 cc Espresso + Cold milk costs 5.0
– println("DoubleMilkCoffee = ${DoubleMilkCoffee().invoice()}")
// DoubleMilkCoffee = 5.0 cc Espresso + Hot milk costs 7.0
Invoice calls
Coffee Shop
Program Example
29
31. Page
– Be careful with ‘this’ usage
– return this
– ‘this’ always aims the class it is in
@snnkzk
Scoped ‘this’
30
32. Page @snnkzk
Scoped ‘this’
interface Beverage {
val volume: Double
val cost: Double
val name: String
fun invoice(): String = "$volume cc $name costs $cost"
fun returnThis(): Beverage
}
31
33. Page @snnkzk
Scoped ‘this’
data class Water(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 0.0
) : Beverage {
override fun returnThis(): Beverage = this
}
data class Milk(
override val name: String,
override val volume: Double,
override val cost: Double = volume * 1
) : Beverage {
override fun returnThis(): Beverage = this
}
data class Espresso(
override val volume: Double = 1.0,
private val base: Beverage = HotWater(volume)
) : Beverage by base {
override val name: String = "Espresso"
override val cost: Double = base.cost + volume * 3
}
32
35. Page @snnkzk
Espresso = Water(name=Hot water, volume=1.0,
cost=0.0)
Scoped ‘this’
println(”Espresso = ${Espresso().returnThis()}")
data class Espresso(
override val volume: Double = 1.0,
private val base: Beverage
) : Beverage by base {
override val name: String = "Espresso
override val cost: Double = …
}
34
36. Page
– Extension on interface works for every class
– Extension override is possible
– Casting is required for using overridden extension
@snnkzk
Extension function parent class vs child class
35
So everyone knows composition/ decorator pattern
I didn’t understand at what kotlin delegation gives us
Inheritance creates base classes
Not possible multi class inheritance
inherited classe can affect each other
Milsoft
Swing
View creating on runtime
It didn’t prevent bugs
ALL we want single line
We had over 1.4 million LoC in previous company
I cannot imagine how much will it reduce with this anda data classes
Depending on total loc / project time.
They calculated a java dev writes 4 LoC per hour.
I really would like to know what will it be with kotlin
Helper for lazy object creation
Gets a block
It has thread safety by default but we can lossen it by passing policy
Actually interface with some tricks nothing more
Expect operator function
If you are using Vm
Activities and fragments needs to create/get VM
Besides different class information code will be identical
If you are using Vm
Activities and fragments needs to create/get VM
Besides different class information code will be identical
For not repeating getString setString methods
No need to repeat editor method
Setting and Getting will use our method
ASH
It is fully tested because it is actually simple 10 lines of class
END
WATER
Compiler magic
Compiler adds every interface method to our class
Next java
It is easy to choose inheritance over this uglyness
Delegation for java developer is hard if interface gets big
Every change to interface and base class force us to change this implementation
Tests for base class needs to be written for new class too
This is what we want
Java doesn’t allow multiple inheritance
Multi Inheritance is pain
Android uses inheritance all over the place
Billion dollar mistake
Jean Michel
Wikipedia example
While creating window we don’t care
We can have different window type on runtime
My example project.
I used similar example from Head First Design Patters book
Data classes
Everything needs to be data classes
So interface for data class
Next MILK
Next ESPRESSO
Final product we can sell in coffee shop
All of them beverages
Everyone of them unique
It is not decorator pattern but example for delegation
Write test as much as possible
Don’t return this
If you return this or use this, override those method in new class
Updaate interface
Updated classes
Espresso didn’t changed because base should alreadty implement new method
Creating a new espresso should return espreso
While reading code we in team expected it will always return espresso. Because that is this right :smile