Delegates and Delegated Properties in Kotlin
2024-10-8 18:25:40 Author: hackernoon.com(查看原文) 阅读量:2 收藏

Hello!

My name is Artem and I am the author of the Android Insights YouTube channel

Today we will enter the world of delegates and delegated properties in Kotlin. This topic might seem complex at first glance, but I'll do my best to explain it as clearly as possible. Let's get started!

What Are Delegates?

Before going into the details, let's clarify some fundamental concepts.

A delegate is an object that another object, well, delegates certain tasks to. In Kotlin, delegation is a powerful tool that allows you to reuse code and implement complex behavior without the need for inheritance.

Example of Delegation

Let's consider a simple example:

interface Base {
    fun print()
}

class BaseImpl(private val x: Int) : Base {
    override fun print() {
        println(x)
    }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print() // Outputs: 10
}

In this example:

  • We define an interface Base with a method print().
  • The class BaseImpl implements this interface and holds a value x.
  • The class Derived delegates the implementation of Base to the object b.

When we call print() on an instance of Derived, it actually executes the print() method of the BaseImpl object. This allows us to reuse code and add flexibility to the application's architecture without excessive inheritance.

Delegated Properties

Now, let's discuss delegated properties.

A delegated property is a property that delegates its getter and setter to another object. The syntax for declaring a delegated property looks like this:

class Example {
    var p: String by Delegate()
}

Here, p is a delegated property. All accesses to p will be redirected to the Delegate() object.

Built-in Delegates in Kotlin

Kotlin provides several built-in delegates that simplify common tasks.

lazy — Lazy Initialization

The lazy delegate is used for lazy initialization of a property. The value is computed only upon the first access.

val lazyValue: String by lazy {
    println("Computing the value...")
    "Hello"
}

fun main() {
    println(lazyValue) // Outputs: Computing the value... Hello
    println(lazyValue) // Outputs: Hello
}

In this example, when we first access lazyValue, the code block inside lazy is executed, and the value is stored for subsequent uses.

observable — Observable Property

The observable delegate allows you to track changes to a property and react accordingly.

import kotlin.properties.Delegates

var name: String by Delegates.observable("Initial value") { prop, old, new ->
    println("$old -> $new")
}

fun main() {
    name = "First"   // Outputs: Initial value -> First
    name = "Second"  // Outputs: First -> Second
}

Here, every time name changes, the code block is executed, printing the old and new values.

Creating Custom Delegates

In addition to built-in delegates, we can create custom delegates to implement specific behavior. This can be done using the ReadOnlyProperty and ReadWriteProperty interfaces, or by directly implementing the getValue and setValue operators.

Using ReadOnlyProperty

Let's create a delegate that always returns the same value and logs each access.

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class ConstantValue<T>(private val value: T) : ReadOnlyProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Getting value of property '${property.name}'")
        return value
    }
}

class Example {
    val constant: String by ConstantValue("Hello, World!")
}

fun main() {
    val example = Example()
    println(example.constant)
    // Outputs:
    // Getting value of property 'constant'
    // Hello, World!
}

Using ReadWriteProperty

Now let's create a delegate that logs read and write operations of a property.

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class LoggingProperty<T>(private var value: T) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Getting value of property '${property.name}': $value")
        return value
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        println("Setting value of property '${property.name}' to $newValue")
        value = newValue
    }
}

class Example {
    var logged: String by LoggingProperty("Initial")
}

fun main() {
    val example = Example()
    println(example.logged)
    example.logged = "New Value"
    println(example.logged)
    // Outputs:
    // Getting value of property 'logged': Initial
    // Initial
    // Setting value of property 'logged' to New Value
    // Getting value of property 'logged': New Value
    // New Value
}

Using ReadOnlyProperty and ReadWriteProperty allows us to explicitly indicate which operations the delegate supports, making the code more readable and understandable.

Direct Implementation of getValue and setValue

Alternatively, we can directly implement the getValue and setValue operators.

import kotlin.reflect.KProperty

class StringDelegate {
    private var value: String = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Getting value of property '${property.name}'")
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("Setting value of property '${property.name}' to $newValue")
        value = newValue
    }
}

class Example {
    var str: String by StringDelegate()
}

fun main() {
    val example = Example()
    example.str = "Hello"
    println(example.str)
    // Outputs:
    // Setting value of property 'str' to Hello
    // Getting value of property 'str'
    // Hello
}

This approach offers more flexibility, allowing us to implement only the necessary methods for our purposes.

Practical Applications of Delegates

Let's explore some practical scenarios where delegates can be particularly useful.

Lazy Initialization of Resource-Intensive Objects

The lazy delegate is perfect for deferred initialization of objects whose creation requires significant resources.

class ResourceManager {
    val database by lazy {
        println("Connecting to the database...")
        Database.connect()
    }
}

fun main() {
    val manager = ResourceManager()
    println("ResourceManager created")
    // The database is not initialized yet
    manager.database.query("SELECT * FROM users")
    // The database is already initialized
    manager.database.query("SELECT * FROM products")
}

In this example, the connection to the database occurs only upon the first access to database, conserving resources until they are actually needed.

Implementing the Observer Pattern

With the observable delegate, we can easily implement the Observer pattern by tracking property changes.

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("") { prop, old, new ->
        println("User's name changed from '$old' to '$new'")
    }
}

fun main() {
    val user = User()
    user.name = "Alice"  // Outputs: User's name changed from '' to 'Alice'
    user.name = "Bob"    // Outputs: User's name changed from 'Alice' to 'Bob'
}

This allows us to perform specific actions when properties change, such as updating the UI, validating data, or sending notifications.

Conclusion

Delegates are a powerful tool in the Kotlin developer's arsenal. They enable you to write cleaner, more modular, and flexible code, opening up new possibilities for implementing complex logic and design patterns.

I hope this guide has helped you better understand delegates and delegated properties in Kotlin. Feel free to experiment and apply these concepts in your own projects!

Thank you for your attention, and happy coding!


文章来源: https://hackernoon.com/delegates-and-delegated-properties-in-kotlin?source=rss
如有侵权请联系:admin#unsafe.sh