50

Delegate Properties in Kotlin – TechShots – Medium

 5 years ago
source link: https://medium.com/techshots/delegate-properties-in-kotlin-39524fc4ea13
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Delegate Properties in Kotlin

Image for post
Image for post

Source

Kotlin provides language support for delegation using Delegated properties. This feature lets you create and implement properties once and use them anywhere.
Delegationis a design pattern where object/property instead of performing a task, it delegates that task to another helper object.

Basics

class Demo {
var name: String by DelegateHelperClass()
}

Here a property name in our Demo class delegates the logic for get()/set() to DelegateHelperClass, so anything after the keyword by which satisfies the convention for property delegates (which we will cover later) act as a Delegate.

Let’s see some standard delegates properties that Kotlin provides out of the box, after that we will through some light upon why do we need delegation as a pattern and then will wrap up by creating some custom delegates.

Standard delegate properties

Lazy
Lazy() is used when we want to delay the initialization of the property until it is accessed for the first time.

class Demo {
val name: String by lazy {
println("Init")
"Dilpreet"
}
}
val d = Demo()
println(d.name)
println(d.name)Output:
Init
Dilpreet
Dilpreet

Observable
You can use this delegate when you want to get notified using callbacks whenever the property changes.

class Employee {
var salary: Float by Delegates.observable(0.0F) {
prop, old, new ->
logSalary(new)
}

private fun logSalary(newSalary: Float) {
println("New Salary entered: $newSalary")
}
}
val employee = Employee()
employee.salary = 100000F
println(employee.salary)Output:
New Salary entered: 100000
100000

Vetoable

Vetoable is similar to Observable but it allows to modify and notify the value only when it meets the condition specified.
Let's take the above example, we don’t want that employee should not be given less than 70000 so we will use vetoable here.

class Employee {
var salary: Float by Delegates.vetoable(70000.0F) {
prop, old, new ->
logSalary(new)
new >= 70000
}

private fun logSalary(newSalary: Float) {
println("New Salary entered: $newSalary")
}
}
val employee = Employee()
println(employee.salary)
employee.salary = 0F
println(employee.salary)
employee.salary = 100000F
println(employee.salary)Output:
70000.0
New Salary entered: 0.0
70000.0
New Salary entered: 100000.0
100000.0

Why Delegation?

Favor Composition over Inheritance

Inheritance helps to achieve code reusability, but a child class depends on the implementation of the parent class and if the parent class implementation changes then it might break the child class. So it is safe to extend a class which is documented for extension but not for classes across package boundaries.

Suppose you wanted to count how many items are added to a set and for that you thought of extending the HashSet class available.

class CountingSet : HashSet<Long>() {

var addedCount: Long = 0
private set

override fun
add(aLong: Long): Boolean {
addedCount++
return super.add(aLong)
}
}

If the devs for HashSet thought of adding the same functionality to their class and coincidently named their variable addedCount i.e. same as yours, this would break your CountingSet class.

In order to overcome these limitations, you can use composition.
You give your new class a private field that references an instance of the existing class. Each instance method in the new class invokes the corresponding method on the contained instance of the existing class and returns the results. This is known as forwarding, and the methods in the new class are known as forwarding methods.

And the combination of composition and forwarding can be loosely referred to as Delegation.

class CountingSet: ForwardingSet<Long>() {
private val delegate: MutableSet<Long> = Sets.newHashSet()
private var addedCount= 0L override fun delegate() = delegate override fun add(element: Long): Boolean {
addedCount++
return delegate().add(element)
}
}

Here we created a private field that references the existing class and then created a function add() which forwards the task to delegate’s method.
This can be further be improved by using Class Delegation provided by Kotlin.

class CountingSet(private val delegate: MutableSet<Long> = HashSet<Long>()): MutableSet<Long> by delegate {

private var addCount = 0L

override fun add(element: Long): Boolean {
addCount++
return delegate.add(element)
}
}

Creating your own property delegators

A property in Kotlin can be either accessed or updated (get()/set()), so kotlin provides with two operators(getValue()/ setValue()) which when implemented to a class can be used to delegate the accessing and updating of that property.
Now let's go back to our example from the very beginning and create our DelegateHelperClass.

class DelegateHelperClass {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Hello $thisRef"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("Updated Value: $thisRef.")
}
}
class Demo {
var name: String by DelegateHelperClass()
}val d = Demo()
d.name = "Dilpreet"
println(d.name)Output:
Updated Value: Dilpreet
Hello Dilpreet

In order to implement a DelegateHelperClass without a need to remember the function parameters, Kotlin provides us with two interfaces.

interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

DatabaseDelegate (Example)

Suppose you have a use case where you want to fetch the list of users from your database. Let's see how we can use property delegates to get this done.

As we only need to read from our Db so we will use ReadOnlyProperty provided by Kotlin.

class DatabaseDelegate<in R, T>(readQuery: String, userId: Any) : ReadOnlyProperty<R, T> {override fun getValue(thisRef: R, property: KProperty<*>): T {
return executeQuery(readQuery, userId)
}
private fun executeQuery(readQuery: String, userId: Any) {....}
}class UserDB(userId: String) {
var name: String by DatabaseDelegate("SELECT name FROM users WHERE userId = :id", userId)
}val userdb = UserDB("2")
userdb.name

When you will call userdb.name it will delegate the get() part to our DatabaseDelegate which contains a private function for executing the query you specified.

Conclusion

Delegate properties in Kotlin will surely help us to write reusable and clean code. If you have any questions or feedback, please let me know in the comments down below.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK