Kotlin Delegated
\\
(#) is a fundamental technique in software design patterns. In the delegation pattern, two objects participate in handling the same request, and the object that receives the request delegates it to another object for processing.\\
\\
Kotlin directly supports the delegation pattern in a more elegant and concise way. Kotlin implements delegation using the `by` keyword.\\
\\
* * *\\
\\
## Class Delegation\\
\\
Class delegation means that the methods defined in a class are actually implemented by calling methods of an object of another class.\\
\\
In the following example, the derived class `Derived` inherits all methods of the interface `Base` and delegates them to an object of `Base` that is passed in.\\
\\
```kotlin\\
// Create interface\\
interface Base {\\
fun print()\\
}\\
\\
// The delegated class that implements this interface\\
class BaseImpl(val x: Int) : Base {\\
override fun print() {\\
print(x)\\
}\\
}\\
\\
// Establish delegation class by keyword by\\
class Derived(b: Base) : Base by b\\
\\
fun main(args: Array) {\\
val b = BaseImpl(10)\\
Derived(b).print() // Output 10\\
}\\
\\
In the `Derived` declaration, the `by` clause indicates that `b` is stored inside the `Derived` object instance, and the compiler will generate implementations for all methods inherited from the `Base` interface, forwarding the calls to `b`.\\
\\
* * *\\
\\
## Property Delegation\\
\\
Property delegation means that the value of a property in a class is not defined directly within the class, but is delegated to a proxy class, thereby achieving unified management of the class's properties.\\
\\
The syntax for property delegation is:\\
\\
```kotlin\\
val/var : by \\
\\
* `var`/`val`: Property type (mutable/read-only)\\
* Property name: The name of the property\\
* Type: The data type of the property\\
* Expression: The delegate proxy class\\
\\
The expression after the `by` keyword is the delegate. The property's `get()` method (and `set()` method) will be delegated to this object's `getValue()` and `setValue()` methods. Property delegates do not need to implement any interface, but must provide a `getValue()` function (and for `var` properties, also a `setValue()` function).\\
\\
### Defining a Delegate Class\\
\\
This class needs to contain `getValue()` and `setValue()` methods, where the parameter `thisRef` is the object of the class being delegated, and `prop` is the object of the property being delegated.\\
\\
```kotlin\\
import kotlin.reflect.KProperty\\
\\
// Define a class containing delegated properties\\
class Example {\\
var p: String by Delegate()\\
}\\
\\
// Delegated class\\
class Delegate {\\
operator fun getValue(thisRef: Any?, property: KProperty): String {\\
return "$thisRef, Here delegated ${property.name} property"\\
}\\
\\
operator fun setValue(thisRef: Any?, property: KProperty, value: String) {\\
println("$thisRef 's ${property.name} propertyAssign the value as $value")\\
}\\
}\\
\\
fun main(args: Array) {\\
val e = Example()\\
println(e.p) // Access the property, call getValue() functions\\
e.p = "Tutorial" // Call setValue() functions\\
println(e.p)\\
}\\
\\
The output is:\\
\\
Example@433c675d, Here delegated p property\\
Example@433c675d 's p propertyAssign the value as Tutorial\\
Example@433c675d, Here delegated p property\\
\\
* * *\\
\\
## Standard Delegation\\
\\
Kotlin's standard library already contains many built-in factory methods to implement property delegation.\\
\\
### Lazy Properties\\
\\
`lazy()` is a function that takes a lambda expression as a parameter and returns a `Lazy` instance. The returned instance can be used as a delegate for implementing lazy properties: the first call to `get()` will execute the lambda expression passed to `lazy()` and record the result, and subsequent calls to `get()` will simply return the recorded result.\\
\\
```kotlin\\
val lazyValue: String by lazy {\\
println("computed!") // First call outputs, second call does not execute\\
"Hello"\\
}\\
\\
fun main(args: Array) {\\
println(lazyValue) // First execution, execute the output expression twice\\
println(lazyValue) // Second execution, only output the return value\\
}\\
\\
The execution output is:\\
\\
computed!\\
Hello\\
Hello\\
\\
* * *\\
\\
## Observable Properties\\
\\
`observable` can be used to implement the observer pattern.\\
\\
The `Delegates.observable()` function takes two parameters: the first is the initial value, and the second is the handler for the property value change event.\\
\\
After the property is assigned, the event handler is executed. It has three parameters: the property being assigned, the old value, and the new value:\\
\\
```kotlin\\
import kotlin.properties.Delegates\\
\\
class User {\\
var name: String by Delegates.observable("Initial value") { prop, old, new ->\\
println("Old value:$old -> New value:$new")\\
}\\
}\\
\\
fun main(args: Array) {\\
val user = User()\\
user.name = "First assignment"\\
user.name = "Second assignment"\\
}\\
\\
The execution output is:\\
\\
Old value:Initial value -> New value: first assignment\\
Old value: first assignment -> New value: second assignment\\
\\
* * *\\
\\
## Storing Properties in Maps\\
\\
A common use case is to store property values in a map. This often occurs in applications that parse JSON or do other "dynamic" things. In this case, you can use the map instance itself as a delegate to implement delegated properties.\\
\\
```kotlin\\
class Site(val map: Map) {\\
val name: String by map\\
val url: String by map\\
}\\
\\
fun main(args: Array) {\\
// Construct functions that accept a map parameter\\
val site = Site(mapOf(\\
"name" to "",\\
"url" to "www."\\
))\\
\\
// Read map value\\
println(site.name)\\
println(site.url)\\
}\\
\\
The execution output is:\\
\\
www.\\
\\
If using `var` properties, you need to change `Map` to `MutableMap`:\\
\\
```kotlin\\
class Site(val map: MutableMap) {\\
val name: String by map\\
val url: String by map\\
}\\
\\
fun main(args: Array) {\\
var map: MutableMap = mutableMapOf(\\
"name" to "",\\
"url" to "www."\\
)\\
val site = Site(map)\\
println(site.name)\\
println(site.url)\\
println("--------------")\\
map.put("name", "Google")\\
map.put("url", "www.google.com")\\
println(site.name)\\
println(site.url)\\
}\\
\\
The execution output is:\\
\\
www.\\
--------------\\
Google\\
www.google.com\\
\\
* * *\\
\\
## Not Null\\
\\
`notNull` is suitable for situations where the property value cannot be determined during the initialization phase.\\
\\
```kotlin\\
class Foo {\\
var notNullBar: String by Delegates.notNull()\\
}\\
\\
foo.notNullBar = "bar"\\
println(foo.notNullBar)\\
\\
Note that if the property is accessed before it is assigned, an exception will be thrown.\\
\\
* * *\\
\\
## Local Delegated Properties\\
\\
You can declare local variables as delegated properties. For example, you can make a local variable lazily initialized:\\
\\
```kotlin\\
fun example(computeFoo: () -> Foo) {\\
val memoizedFoo by lazy(computeFoo)\\
if (someCondition && memoizedFoo.isValid()) {\\
memoizedFoo.doSomething()\\
}\\
}\\
\\
The `memoizedFoo` variable will only be computed on the first access. If `someCondition` fails, the variable will not be computed at all.\\
\\
* * *\\
\\
## Property Delegation Requirements\\
\\
For a read-only property (i.e., a `val` property), its delegate must provide a function named `getValue()`. This function accepts the following parameters:\\
\\
* `thisRef` β Must be of the same type as the property owner (for extension properties, the type being extended) or its supertype.\\
* `property` β Must be of type `KProperty` or its supertype.\\
\\
This function must return the same type as the property (or its subtype).\\
\\
For a mutable property (i.e., a `var` property), in addition to the `getValue()` function, its delegate must also provide another function named `setValue()`. This function accepts the following parameters:\\
\\
* `property` β Must be of type `KProperty` or its supertype.\\
* `new value` β Must be of the same type as the property or its supertype.\\
\\
* * *\\
\\
## Under the Hood\\
\\
Behind the implementation of each delegated property, the Kotlin compiler generates a helper property and delegates to it. For example, for a property `prop`, a hidden property `prop$delegate` is generated, and the accessor code simply delegates to this additional property:\\
\\
```kotlin\\
class C {\\
var prop: Type by MyDelegate()\\
}\\
\\
// This is the corresponding code generated by the compiler:\\
class C {\\
private val prop$delegate = MyDelegate()\\
var prop: Type\\
get() = prop$delegate.getValue(this, this::prop)\\
set(value: Type) = prop$delegate.setValue(this, this::prop, value)\\
}\\
\\
The Kotlin compiler provides all necessary information about `prop` in the parameters: the first parameter `this` refers to the instance of the outer class `C`, and `this::prop` is a reflection object of type `KProperty` that describes `prop` itself.\\
\\
* * *\\
\\
## Providing Delegates\\
\\
By defining the `provideDelegate` operator, you can extend the logic of creating the delegate object for property implementation. If the object used on the right side of `by` defines `provideDelegate` as a member or extension function, that function will be called to create the property delegate instance.\\
\\
A possible use case for `provideDelegate` is to check property consistency when creating the property (not just in its getter or setter).\\
\\
For example, if you want to check the property name before binding, you can write it like this:\\
\\
```kotlin\\
class ResourceLoader(id: ResourceID) {\\
operator fun provideDelegate(\\
thisRef: MyUI,\\
prop: KProperty\\
): ReadOnlyProperty {\\
checkProperty(thisRef, prop.name)\\
// Create delegation\\
}\\
\\
private fun checkProperty(thisRef: MyUI, name: String) {\\
β¦β¦\\
}\\
}\\
\\
fun bindResource(id: ResourceID): ResourceLoader {\\
β¦β¦\\
}\\
\\
class MyUI {\\
val image by bindResource(ResourceID.image_id)\\
val text by bindResource(ResourceID.text_id)\\
}\\
\\
The parameters of `provideDelegate` are the same as `getValue`:\\
\\
* `thisRef` β Must be of the same type as the property owner (for extension properties, the type being extended) or its supertype.\\
* `property` β Must be of type `KProperty` or its supertype.\\
\\
During the creation of the `MyUI` instance, the `provideDelegate` method is called for each property, and the necessary validation is performed immediately.\\
\\
Without this ability to intercept the binding between the property and its delegate, to achieve the same functionality, you would have to explicitly pass the property name, which is not very convenient:\\
\\
```kotlin\\
// Check property name without using the βprovideDelegateβ feature\\
class MyUI {\\
val image by bindResource(ResourceID.image_id, "image")\\
val text by bindResource(ResourceID.text_id, "text")\\
}\\
\\
fun MyUI.bindResource(\\
id: ResourceID,\\
propertyName: String\\
): ReadOnlyProperty {\\
checkProperty(this, propertyName)\\
// Create delegation\\
}\\
\\
In the generated code, the `provideDelegate` method is called to initialize the helper `prop$delegate` property. Compare the code generated for the property declaration `val prop: Type by MyDelegate()` with the code generated above (when the `provideDelegate` method does not exist):\\
\\
```kotlin\\
class C {\\
var prop: Type by MyDelegate()\\
}\\
\\
// This code is whenβprovideDelegateβWhen feature is available\\
// Code generated by the compiler:\\
class C {\\
// CallβprovideDelegateβTo create additionalβdelegateβproperty\\
private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)\\
val prop: Type\\
get() = prop$delegate.getValue(this, this::prop)\\
}\\
\\
Please note that the `provideDelegate` method only affects the creation of the helper property and does not affect the code generated for the getter or setter.
YouTip