Kotlin delegates
in practice
Fabio Collini
@fabioCollini
“”
“The ratio of time spent reading (code)

versus writing is well over 10 to 1

(therefore) making it easy to read

makes it easier to write

Robert C. Martin
“”
“The Principle of Least Astonishment states that

the result of performing some operation should be
obvious, consistent, and predictable, based upon
the name of the operation and other clues

https://wiki.c2.com/?PrincipleOfLeastAstonishment
Kotlin code is readable
Data classes
Extension functions
Sealed classes
Coroutines
Delegates
…
Delegates
public class MyEquivalentJavaClass {
private final String property = slowMethod();
public String getProperty() {
return property;
}5
}6
class MyClass {
val property = slowMethod()
}1
public class MyEquivalentJavaClass {
private String property = slowMethod();
public String getProperty() {
return property;
}
public void setProperty(String var1) {
this.property = var1;
}5
}6
class MyClass {
var property = slowMethod()
}1
public class MyEquivalentJavaClass {
private String property = slowMethod();
}6
class MyClass {
private var property = slowMethod()
}1
public class MyEquivalentJavaClass {
public String getProperty() {
return slowMethod();
}5
}6
class MyClass {
val property get() = slowMethod()
}1
class MyClass {
val property = slowMethod()
}1
fun main() {
val obj = MyClass()
println(obj.property)A
println(obj.property)B
}2
class MyClass {
val property = slowMethod()
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
slowMethod invoked
obj created
end
class MyClass {
val property get() = slowMethod()
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
obj created
slowMethod invoked
slowMethod invoked
end
class MyClass {
val property by lazy {
slowMethod()
}3
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
obj created
slowMethod invoked
end
class MyClass {
val property by lazy {
slowMethod()
}3
}1
by
public class MyEquivalentJavaClass {
private SimpleLazy lazy = new SimpleLazy(::slowMethod);
public String getProperty() {
return lazy.getValue();
}
}
internal object UNINITIALIZED_VALUE
class SimpleLazy<T>(private val initializer: () -> T) {
private var value: Any? = UNINITIALIZED_VALUE
fun getValue(): T {
if (value == UNINITIALIZED_VALUE) {
value = initializer()
}4
return value as T
}3
}2
/**
* Specifies how a Lazy instance synchronizes initialization among multiple threads.
*/
public enum class LazyThreadSafetyMode {
/**
* Locks are used to ensure that only a single thread can initialize the Lazy instance.
*/
SYNCHRONIZED,
/**
* Initializer function can be called several times on concurrent access to
* uninitialized Lazy instance value,
* but only the first returned value will be used as the value of Lazy instance.
*/
PUBLICATION,
/**
* No locks are used to synchronize an access to the Lazy instance value;
* if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the Lazy instance is guaranteed never to
* be initialized from more than one thread.
*/
NONE
}
Default value
class MyClass {
val notAGoodIdea by lazy {
2 + 2
}3
}1
class MyClass {
suspend val property by lazy {
slowMethod()
}1
}2
suspend fun slowMethod() = withContext(IO) {
//...
}3
Modifier 'suspend' is not applicable to 'member property with delegate'
Suspend function 'slowMethod' should be called only from a coroutine or another suspend function
class MyFragment : Fragment() {
private val appName by lazy { getString(R.string.app_name) }1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Starting $appName...")
}2
}3
Lazy
class MyFragment : Fragment() {
private lateinit var appName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
println("Starting $appName...")
}4
}5
lateinit
class MyFragment : Fragment() {
private lateinit var appName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
println("Starting $appName...")
}4
}5
lateinit
class MyFragment : Fragment() {
private val appName by lazy { getString(R.string.app_name) }1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Starting $appName...")
}2
}3
Lazy
class MyFragment : Fragment() {
private lateinit var appName: String
private lateinit var title: String
private lateinit var summary: String
private lateinit var text: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
title = getString(R.string.title)
summary = getString(R.string.summary)
text = "$appNamen$titlen$summary"
println(text)
}4
}5
lateinit class MyFragment2 : Fragment() {
private val appName by lazy { getString(R.string.app_name) }
private val title by lazy { getString(R.string.title) }
private val summary by lazy { getString(R.string.summary) }
private val text by lazy { "$appNamen$titlen$summary" }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println(text)
}2
}3
Lazy
Custom
delegates
operator fun getValue(thisRef: Any, property: KProperty<*>): T
/**
* Base interface that can be used for implementing property delegates of read-only properties.
*1
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*2
* @param R the type of object which owns the delegated property.
* @param T the type of the property value.
*/
interface ReadOnlyProperty<in R, out T> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
operator fun getValue(thisRef: R, property: KProperty<*>): T
}3
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class MyClass {
val property by LogDelegate("ABC")
}3
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class MyClass {
val property by LogDelegate("ABC")
}3
fun main() {
val obj = MyClass()
println(obj.property)
}4
get invoked on MyClass@87aac27.property
ABC
/**
* Base interface that can be used for implementing property delegates of read-write properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param R the type of object which owns the delegated property.
* @param T the type of the property value.
*/
interface ReadWriteProperty<in R, T> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
operator fun getValue(thisRef: R, property: KProperty<*>): T
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
class LogDelegate<T>(initialValue: T) : ReadWriteProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
println("set invoked on $thisRef.${property.name} with value $value")
this.value = value
}5
}2
class MyClass {
var property by LogDelegate("ABC")
}3
fun main() {
val obj = MyClass()
println(obj.property)
obj.property = "DEF"
println(obj.property)
}4
get invoked on MyClass@e9e54c2.property
ABC
set invoked on MyClass@e9e54c2.property with value DEF
get invoked on MyClass@e9e54c2.property
DEF
A real
example
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
fun main() {
val tokenHolder = TokenHolder(sharedPreferences())
tokenHolder.saveToken("ABC")
println("${tokenHolder.token} - ${tokenHolder.count}")
tokenHolder.saveToken("DEF")
println("${tokenHolder.token} - ${tokenHolder.count}")
}
ABC - 1
DEF - 2
fun SharedPreferences.int() =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(property.name, 0)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(property.name, value) }
}1
fun SharedPreferences.int() =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(property.name, 0)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(property.name, value) }
}1
fun SharedPreferences.int(key: String, defaultValue: Int = 0) =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(key, value) }
}1
fun SharedPreferences.int(key: String, defaultValue: Int = 0) =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(key, value) }
}1
fun SharedPreferences.string(key: String, defaultValue: String? = null) =
object : ReadWriteProperty<Any, String?> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getString(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) =
edit { putString(key, value) }
}2
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN,0null)1
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string(TOKEN)1
private3set
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string(TOKEN)1
private set
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")1
private set
val count: Int
get() = prefs.getInt(COUNT, 0)2
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int(COUNT)2
private set
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int(COUNT)2
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int("count")2
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
}4
class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int("count")
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
}4
prefs.edit {
putInt("count", prefs.getInt("count", 0) + 1)
}
class DemoFragment : Fragment() {
private val component by lazy {
//...
}
private val viewModel by viewModelProvider {
component.myViewModel()
}
//...
}
https://proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
class DemoFragment : Fragment() {
private var param1: Int by argument()
private var param2: String by argument()
companion object {
fun newInstance(param1: Int, param2: String): DemoFragment =
DemoFragment().apply {
this.param1 = param1
this.param2 = param2
}
}
}
https://proandroiddev.com/kotlin-delegates-in-android-1ab0a715762d
Standard
delegates
object AnalyticsLib {
fun trackEvent(event: Map<String, Any?>) {
println(event)
}1
}2
fun main() {
val event = mapOf(
"name" to "myEvent",
"value" to 123
)3
AnalyticsLib.trackEvent(event)
}4
fun main() {
val event = mapOf(
"name" to "myEvent",
"value" to 123
)3
AnalyticsLib.trackEvent(event)
}4
const val NAME = "name"
const val VALUE = "value"
fun main() {
val event = mapOf(
NAME to "myEvent",
VALUE to 123
)3
AnalyticsLib.trackEvent(event)
}4
const val NAME = "name"
const val VALUE = "value"
fun main() {
val event = mapOf(
NAME to "myEvent",
VALUE to "this should be an Int :("
)3
AnalyticsLib.trackEvent(event)
}4
class MyEvent {
val map: MutableMap<String, Any?> = mutableMapOf()
var name: String by map
var value: Int by map
}1
fun main() {
val event = MyEvent().apply {
name = "myEvent"
value = 123
}2
AnalyticsLib.trackEvent(event.map)
}3
data class MyEvent(val name: String, val value: Int) {
val map = mapOf(
"name" to name,
"value" to value
)
}1
fun main() {
val event = MyEvent(
name = "myEvent",
value = 123
)2
AnalyticsLib.trackEvent(event.map)
}3
object AbTestLib {
fun readValues() = mapOf<String, Any?>(
"featureEnabled" to true,
"delay" to 1000
)1
}2
object AbTestLib {
fun readValues() = mapOf<String, Any?>(
"featureEnabled" to true,
"delay" to 1000
)1
}2
fun main() {
val values = AbTestLib.readValues()
println(values["featureEnabled"] as Boolean)
println(values["delay"] as Int)
}3
true
1000
data class AbValues(private val map: Map<String, Any?>) {
val featureEnabled: Boolean by map
val delay: Int by map
}1
fun main() {
val values = AbValues(AbTestLib.readValues())
println(values.featureEnabled)
println(values.delay)
}2
data class AbValues(private val map: Map<String, Any?>) {
val featureEnabled: Boolean by map
val delay: Int by map
}1
fun main() {
val values = AbValues(
mapOf<String, Any?>(
"featureEnabled" to "true",
"delay" to "1000"
)
)
println(values.featureEnabled)
println(values.delay)
}2
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
class User {
var name: String by Delegates.observable("<no name>") { prop, old, new ->
println("$old -> $new")
}
}
class User {
var name: String by Delegates.vetoable("<no name>") { prop, old, new ->
new.startsWith("f")
}
}
ObservableVetoable
class MyClass {
var myIntVar: Int by notNull()
fun onCreate() {
myIntVar = calculateValue()
}
}
notNull
Inheritance
Composition
Delegation
Inheritance
open class Class1 {
fun doSomething() = 123
}1
class Class2 : Class1()
Composition
class Class1 {
fun doSomething() = 123
}
class Class2 {
private val wrapped = Class1()
fun doSomething() = wrapped.doSomething()
}
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2(private val wrapped: Class1 = Class1())
: Interface1 by wrapped
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2 : Interface1 by Class1()
11
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2 : Interface1 by Class1()
fun main() {
val obj = Class2()
println(obj.doSomething())
}m
Composition
class Class1 {
fun doSomething() = 123
}2
class Class2 {
private val wrapped = Class1()
fun doSomething() =
wrapped.doSomething()
}3
Inheritance
open class Class1 {
fun doSomething() = 123
}1
class Class2 : Class1()
interface HasMargin {
val marginBottom: Int
val marginTop: Int
val marginLeft: Int
val marginRight: Int
}1
class Style(
val backgroundColor: Int,
override val marginBottom: Int,
override val marginTop: Int,
override val marginLeft: Int,
override val marginRight: Int
) : HasMargin
class View(style: Style) : HasMargin by style {
fun draw() {
//...
}
}
[
{
"type": "student",
"name": "studentName",
"surname": "studentSurname",
"age": 20,
"university": "universityName"
},
{
//...
}
]
data class PersonJson(
val type: String,
val name: String,
val surname: String,
val age: Int,
val university: String?,
val company: String?
)
abstract class Person(
val name: String,
val surname: String,
val age: Int
)
class Student(
name: String,
surname: String,
age: Int,
val university: String
) : Person(name, surname, age)
class Worker(
name: String,
surname: String,
age: Int,
val company: String
) : Person(name, surname, age)
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!
)
else
Worker(
it.name,
it.surname,
it.age,
it.company!!
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
abstract class Person(
val_name: String,
val_surname: String,
val_age: Int
)
class Student(
name: String,
surname: String,
age: Int,
val university: String
) : Person(name, surname, age)_
class Worker(
name: String,
surname: String,
age: Int,
val company: String
) : Person(name, surname, age)_
abstract class Person {
abstract val_name: String
abstract val_surname: String
abstract val_age: Int
}1
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person()_
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person()_
interface Person {
val name: String
val surname: String
val age: Int
}1
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
interface Person {
val name: String
val surname: String
val age: Int
}1
interface Person {
val name: String
val surname: String
val age: Int
}1
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int
) : Person
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age
)
if (it.type == "student")
Student(data, it.university!!)
else
Worker(data, it.company!!)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
}
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!
)
else
Worker(
it.name,
it.surname,
it.age,
it.company!!
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
}
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age
)
if (it.type == "student")
Student(data, it.university!!)
else
Worker(data, it.company!!)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
DelegationInheritance
interface Person {
val name: String
val surname: String
val age: Int
val address: String
val city: String
val zipCode: String
val nation: String
val telephoneNumber1: String
val telephoneNumber2: String
val telephoneNumber3: String
}
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Unemployed(
override val name: String,
override val surname: String,
override val age: Int,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
else if (it.type == "worker")
Worker(
it.name,
it.surname,
it.age,
it.company!!,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
else
Unemployed(
it.name,
it.surname,
it.age,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
val address: String
val city: String
val zipCode: String
val nation: String
val telephoneNumber1: String
val telephoneNumber2: String
val telephoneNumber3: String
}
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
data class Unemployed(
val data: PersonData
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
if (it.type == "student")
Student(data, it.university!!)
else if (it.type == "worker")
Worker(data, it.company!!)
else
Unemployed(data)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
DelegationInheritance
Wrappingup
“”
“The ratio of time spent reading (code)

versus writing is well over 10 to 1

(therefore) making it easy to read

makes it easier to write

Robert C. Martin
Wrappingup
“”
“The Principle of Least Astonishment states that

the result of performing some operation should be
obvious, consistent, and predictable, based upon
the name of the operation and other clues

https://wiki.c2.com/?PrincipleOfLeastAstonishment
Wrappingup
Delegates can be useful to simplify code
but there are pros and cons!
Links&contacts
Simpler Kotlin class hierarchies using class delegation
proandroiddev.com/simpler-kotlin-class-hierarchies-using-class-delegation-35464106fed5
Kotlin delegates in Android development — Part 1
medium.com/hackernoon/kotlin-delegates-in-android-development-part-1-50346cf4aed7
Kotlin delegates in Android development — Part 2
proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
@fabioCollini
linkedin.com/in/fabiocollini
github.com/fabioCollini
medium.com/@fabioCollini
THANKS
FOR YOUR
ATTENTION
QUESTIONS?
@fabioCollini

Kotlin Delegates in practice - Kotlin community conf

  • 1.
  • 2.
    “” “The ratio oftime spent reading (code) versus writing is well over 10 to 1 (therefore) making it easy to read makes it easier to write Robert C. Martin
  • 3.
    “” “The Principle of LeastAstonishment states that the result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues https://wiki.c2.com/?PrincipleOfLeastAstonishment
  • 4.
    Kotlin code isreadable Data classes Extension functions Sealed classes Coroutines Delegates …
  • 5.
  • 6.
    public class MyEquivalentJavaClass{ private final String property = slowMethod(); public String getProperty() { return property; }5 }6 class MyClass { val property = slowMethod() }1
  • 7.
    public class MyEquivalentJavaClass{ private String property = slowMethod(); public String getProperty() { return property; } public void setProperty(String var1) { this.property = var1; }5 }6 class MyClass { var property = slowMethod() }1
  • 8.
    public class MyEquivalentJavaClass{ private String property = slowMethod(); }6 class MyClass { private var property = slowMethod() }1
  • 9.
    public class MyEquivalentJavaClass{ public String getProperty() { return slowMethod(); }5 }6 class MyClass { val property get() = slowMethod() }1
  • 10.
    class MyClass { valproperty = slowMethod() }1 fun main() { val obj = MyClass() println(obj.property)A println(obj.property)B }2
  • 11.
    class MyClass { valproperty = slowMethod() }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting slowMethod invoked obj created end
  • 12.
    class MyClass { valproperty get() = slowMethod() }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting obj created slowMethod invoked slowMethod invoked end
  • 13.
    class MyClass { valproperty by lazy { slowMethod() }3 }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting obj created slowMethod invoked end
  • 14.
    class MyClass { valproperty by lazy { slowMethod() }3 }1
  • 15.
  • 16.
    public class MyEquivalentJavaClass{ private SimpleLazy lazy = new SimpleLazy(::slowMethod); public String getProperty() { return lazy.getValue(); } }
  • 17.
    internal object UNINITIALIZED_VALUE classSimpleLazy<T>(private val initializer: () -> T) { private var value: Any? = UNINITIALIZED_VALUE fun getValue(): T { if (value == UNINITIALIZED_VALUE) { value = initializer() }4 return value as T }3 }2
  • 18.
    /** * Specifies howa Lazy instance synchronizes initialization among multiple threads. */ public enum class LazyThreadSafetyMode { /** * Locks are used to ensure that only a single thread can initialize the Lazy instance. */ SYNCHRONIZED, /** * Initializer function can be called several times on concurrent access to * uninitialized Lazy instance value, * but only the first returned value will be used as the value of Lazy instance. */ PUBLICATION, /** * No locks are used to synchronize an access to the Lazy instance value; * if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the Lazy instance is guaranteed never to * be initialized from more than one thread. */ NONE } Default value
  • 19.
    class MyClass { valnotAGoodIdea by lazy { 2 + 2 }3 }1
  • 20.
    class MyClass { suspendval property by lazy { slowMethod() }1 }2 suspend fun slowMethod() = withContext(IO) { //... }3 Modifier 'suspend' is not applicable to 'member property with delegate' Suspend function 'slowMethod' should be called only from a coroutine or another suspend function
  • 22.
    class MyFragment :Fragment() { private val appName by lazy { getString(R.string.app_name) }1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("Starting $appName...") }2 }3 Lazy
  • 23.
    class MyFragment :Fragment() { private lateinit var appName: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) println("Starting $appName...") }4 }5 lateinit
  • 24.
    class MyFragment :Fragment() { private lateinit var appName: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) println("Starting $appName...") }4 }5 lateinit class MyFragment : Fragment() { private val appName by lazy { getString(R.string.app_name) }1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("Starting $appName...") }2 }3 Lazy
  • 25.
    class MyFragment :Fragment() { private lateinit var appName: String private lateinit var title: String private lateinit var summary: String private lateinit var text: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) title = getString(R.string.title) summary = getString(R.string.summary) text = "$appNamen$titlen$summary" println(text) }4 }5 lateinit class MyFragment2 : Fragment() { private val appName by lazy { getString(R.string.app_name) } private val title by lazy { getString(R.string.title) } private val summary by lazy { getString(R.string.summary) } private val text by lazy { "$appNamen$titlen$summary" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println(text) }2 }3 Lazy
  • 26.
  • 27.
    operator fun getValue(thisRef:Any, property: KProperty<*>): T
  • 28.
    /** * Base interfacethat can be used for implementing property delegates of read-only properties. *1 * This is provided only for convenience; you don't have to extend this interface * as long as your property delegate has methods with the same signatures. *2 * @param R the type of object which owns the delegated property. * @param T the type of the property value. */ interface ReadOnlyProperty<in R, out T> { /** * Returns the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @return the property value. */ operator fun getValue(thisRef: R, property: KProperty<*>): T }3
  • 29.
    class LogDelegate<T>(initialValue: T): ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2
  • 30.
    class LogDelegate<T>(initialValue: T): ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2 class MyClass { val property by LogDelegate("ABC") }3
  • 31.
    class LogDelegate<T>(initialValue: T): ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2 class MyClass { val property by LogDelegate("ABC") }3 fun main() { val obj = MyClass() println(obj.property) }4 get invoked on MyClass@87aac27.property ABC
  • 32.
    /** * Base interfacethat can be used for implementing property delegates of read-write properties. * * This is provided only for convenience; you don't have to extend this interface * as long as your property delegate has methods with the same signatures. * * @param R the type of object which owns the delegated property. * @param T the type of the property value. */ interface ReadWriteProperty<in R, T> { /** * Returns the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @return the property value. */ operator fun getValue(thisRef: R, property: KProperty<*>): T /** * Sets the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @param value the value to set. */ operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
  • 33.
    class LogDelegate<T>(initialValue: T): ReadWriteProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("set invoked on $thisRef.${property.name} with value $value") this.value = value }5 }2 class MyClass { var property by LogDelegate("ABC") }3 fun main() { val obj = MyClass() println(obj.property) obj.property = "DEF" println(obj.property) }4 get invoked on MyClass@e9e54c2.property ABC set invoked on MyClass@e9e54c2.property with value DEF get invoked on MyClass@e9e54c2.property DEF
  • 34.
  • 35.
    class TokenHolder(private valprefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 36.
    class TokenHolder(private valprefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 37.
    class TokenHolder(private valprefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 38.
    class TokenHolder(private valprefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 39.
    fun main() { valtokenHolder = TokenHolder(sharedPreferences()) tokenHolder.saveToken("ABC") println("${tokenHolder.token} - ${tokenHolder.count}") tokenHolder.saveToken("DEF") println("${tokenHolder.token} - ${tokenHolder.count}") } ABC - 1 DEF - 2
  • 40.
    fun SharedPreferences.int() = object: ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(property.name, 0) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(property.name, value) } }1
  • 41.
    fun SharedPreferences.int() = object: ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(property.name, 0) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(property.name, value) } }1
  • 42.
    fun SharedPreferences.int(key: String,defaultValue: Int = 0) = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(key, value) } }1
  • 43.
    fun SharedPreferences.int(key: String,defaultValue: Int = 0) = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(key, value) } }1 fun SharedPreferences.string(key: String, defaultValue: String? = null) = object : ReadWriteProperty<Any, String?> { override fun getValue(thisRef: Any, property: KProperty<*>) = getString(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) = edit { putString(key, value) } }2
  • 44.
    class TokenHolder(private valprefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN,0null)1 val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 45.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string(TOKEN)1 private3set val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 46.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string(TOKEN)1 private set val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 47.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string("token")1 private set val count: Int get() = prefs.getInt(COUNT, 0)2 fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val COUNT = "count" }3 }4
  • 48.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int(COUNT)2 private set fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val COUNT = "count" }3 }4
  • 49.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int(COUNT)2 private set fun saveToken(newToken: String) { token = newToken count++ }2 companion object { private const val COUNT = "count" }3 }4
  • 50.
    class TokenHolder(private valprefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int("count")2 private set fun saveToken(newToken: String) { token = newToken count++ }2 }4
  • 51.
    class TokenHolder(prefs: SharedPreferences){ var token by prefs.string("token") private set var count by prefs.int("count") private set fun saveToken(newToken: String) { token = newToken count++ }2 }4 prefs.edit { putInt("count", prefs.getInt("count", 0) + 1) }
  • 52.
    class DemoFragment :Fragment() { private val component by lazy { //... } private val viewModel by viewModelProvider { component.myViewModel() } //... } https://proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
  • 53.
    class DemoFragment :Fragment() { private var param1: Int by argument() private var param2: String by argument() companion object { fun newInstance(param1: Int, param2: String): DemoFragment = DemoFragment().apply { this.param1 = param1 this.param2 = param2 } } } https://proandroiddev.com/kotlin-delegates-in-android-1ab0a715762d
  • 54.
  • 55.
    object AnalyticsLib { funtrackEvent(event: Map<String, Any?>) { println(event) }1 }2 fun main() { val event = mapOf( "name" to "myEvent", "value" to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 56.
    fun main() { valevent = mapOf( "name" to "myEvent", "value" to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 57.
    const val NAME= "name" const val VALUE = "value" fun main() { val event = mapOf( NAME to "myEvent", VALUE to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 58.
    const val NAME= "name" const val VALUE = "value" fun main() { val event = mapOf( NAME to "myEvent", VALUE to "this should be an Int :(" )3 AnalyticsLib.trackEvent(event) }4
  • 59.
    class MyEvent { valmap: MutableMap<String, Any?> = mutableMapOf() var name: String by map var value: Int by map }1 fun main() { val event = MyEvent().apply { name = "myEvent" value = 123 }2 AnalyticsLib.trackEvent(event.map) }3
  • 60.
    data class MyEvent(valname: String, val value: Int) { val map = mapOf( "name" to name, "value" to value ) }1 fun main() { val event = MyEvent( name = "myEvent", value = 123 )2 AnalyticsLib.trackEvent(event.map) }3
  • 61.
    object AbTestLib { funreadValues() = mapOf<String, Any?>( "featureEnabled" to true, "delay" to 1000 )1 }2
  • 62.
    object AbTestLib { funreadValues() = mapOf<String, Any?>( "featureEnabled" to true, "delay" to 1000 )1 }2 fun main() { val values = AbTestLib.readValues() println(values["featureEnabled"] as Boolean) println(values["delay"] as Int) }3 true 1000
  • 63.
    data class AbValues(privateval map: Map<String, Any?>) { val featureEnabled: Boolean by map val delay: Int by map }1 fun main() { val values = AbValues(AbTestLib.readValues()) println(values.featureEnabled) println(values.delay) }2
  • 64.
    data class AbValues(privateval map: Map<String, Any?>) { val featureEnabled: Boolean by map val delay: Int by map }1 fun main() { val values = AbValues( mapOf<String, Any?>( "featureEnabled" to "true", "delay" to "1000" ) ) println(values.featureEnabled) println(values.delay) }2 Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
  • 65.
    class User { varname: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } class User { var name: String by Delegates.vetoable("<no name>") { prop, old, new -> new.startsWith("f") } } ObservableVetoable
  • 66.
    class MyClass { varmyIntVar: Int by notNull() fun onCreate() { myIntVar = calculateValue() } } notNull
  • 67.
  • 68.
    Inheritance open class Class1{ fun doSomething() = 123 }1 class Class2 : Class1()
  • 69.
    Composition class Class1 { fundoSomething() = 123 } class Class2 { private val wrapped = Class1() fun doSomething() = wrapped.doSomething() }
  • 70.
    Delegation interface Interface1 { fundoSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2(private val wrapped: Class1 = Class1()) : Interface1 by wrapped
  • 71.
    Delegation interface Interface1 { fundoSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2 : Interface1 by Class1() 11
  • 72.
    Delegation interface Interface1 { fundoSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2 : Interface1 by Class1() fun main() { val obj = Class2() println(obj.doSomething()) }m Composition class Class1 { fun doSomething() = 123 }2 class Class2 { private val wrapped = Class1() fun doSomething() = wrapped.doSomething() }3 Inheritance open class Class1 { fun doSomething() = 123 }1 class Class2 : Class1()
  • 73.
    interface HasMargin { valmarginBottom: Int val marginTop: Int val marginLeft: Int val marginRight: Int }1 class Style( val backgroundColor: Int, override val marginBottom: Int, override val marginTop: Int, override val marginLeft: Int, override val marginRight: Int ) : HasMargin class View(style: Style) : HasMargin by style { fun draw() { //... } }
  • 74.
    [ { "type": "student", "name": "studentName", "surname":"studentSurname", "age": 20, "university": "universityName" }, { //... } ] data class PersonJson( val type: String, val name: String, val surname: String, val age: Int, val university: String?, val company: String? )
  • 75.
    abstract class Person( valname: String, val surname: String, val age: Int ) class Student( name: String, surname: String, age: Int, val university: String ) : Person(name, surname, age) class Worker( name: String, surname: String, age: Int, val company: String ) : Person(name, surname, age)
  • 76.
    fun main() { valjson: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!! ) else Worker( it.name, it.surname, it.age, it.company!! ) } println(people.joinToString { "${it.name} ${it.surname}" }) }
  • 77.
    abstract class Person( val_name:String, val_surname: String, val_age: Int ) class Student( name: String, surname: String, age: Int, val university: String ) : Person(name, surname, age)_ class Worker( name: String, surname: String, age: Int, val company: String ) : Person(name, surname, age)_
  • 78.
    abstract class Person{ abstract val_name: String abstract val_surname: String abstract val_age: Int }1 data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person()_ data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person()_
  • 79.
    interface Person { valname: String val surname: String val age: Int }1 data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person
  • 80.
    interface Person { valname: String val surname: String val age: Int }1
  • 81.
    interface Person { valname: String val surname: String val age: Int }1 data class PersonData( override val name: String, override val surname: String, override val age: Int ) : Person
  • 82.
    data class Student( overrideval name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person
  • 83.
    data class Student( overrideval name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data
  • 84.
    fun main() { valjson: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age ) if (it.type == "student") Student(data, it.university!!) else Worker(data, it.company!!) } println(people.joinToString { "${it.name} ${it.surname}" }) }
  • 85.
    interface Person { valname: String val surname: String val age: Int } data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!! ) else Worker( it.name, it.surname, it.age, it.company!! ) } println(people.joinToString { "${it.name} ${it.surname}" }) } interface Person { val name: String val surname: String val age: Int } data class PersonData( override val name: String, override val surname: String, override val age: Int ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age ) if (it.type == "student") Student(data, it.university!!) else Worker(data, it.company!!) } println(people.joinToString { "${it.name} ${it.surname}" }) } DelegationInheritance
  • 86.
    interface Person { valname: String val surname: String val age: Int val address: String val city: String val zipCode: String val nation: String val telephoneNumber1: String val telephoneNumber2: String val telephoneNumber3: String } data class Student( override val name: String, override val surname: String, override val age: Int, val university: String, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Unemployed( override val name: String, override val surname: String, override val age: Int, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!!, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) else if (it.type == "worker") Worker( it.name, it.surname, it.age, it.company!!, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) else Unemployed( it.name, it.surname, it.age, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) } println(people.joinToString { "${it.name} ${it.surname}" }) } interface Person { val name: String val surname: String val age: Int val address: String val city: String val zipCode: String val nation: String val telephoneNumber1: String val telephoneNumber2: String val telephoneNumber3: String } data class PersonData( override val name: String, override val surname: String, override val age: Int, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data data class Unemployed( val data: PersonData ) : Person by data fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) if (it.type == "student") Student(data, it.university!!) else if (it.type == "worker") Worker(data, it.company!!) else Unemployed(data) } println(people.joinToString { "${it.name} ${it.surname}" }) } DelegationInheritance
  • 87.
    Wrappingup “” “The ratio oftime spent reading (code) versus writing is well over 10 to 1 (therefore) making it easy to read makes it easier to write Robert C. Martin
  • 88.
    Wrappingup “” “The Principle of LeastAstonishment states that the result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues https://wiki.c2.com/?PrincipleOfLeastAstonishment
  • 89.
    Wrappingup Delegates can beuseful to simplify code but there are pros and cons!
  • 90.
    Links&contacts Simpler Kotlin classhierarchies using class delegation proandroiddev.com/simpler-kotlin-class-hierarchies-using-class-delegation-35464106fed5 Kotlin delegates in Android development — Part 1 medium.com/hackernoon/kotlin-delegates-in-android-development-part-1-50346cf4aed7 Kotlin delegates in Android development — Part 2 proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438 @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini
  • 91.
  • 92.