Kotlin Delegated Properties: kotlinlang.org/docs/delega…

Kotlin Delegation: kotlinlang.org/docs/delega…

Kotlin provides the ability to quickly implement proxy patterns at the language level to reduce the presence of template code.

Delegate patterns in design patterns

The delegate pattern is defined as: two objects participate in processing the same request, and the receiving object delegates the request to the other object.

A simple example:

 class RealPrinter { // The "delegate" actually performs the logic (the object that handles the request)
     void print(a) { 
       System.out.print("something"); }}class Printer { // the "delegator" 
     RealPrinter p = new RealPrinter(); // Create the delegate to execute the logic (the object that receives the request)
     void print(a) { 
       p.print(); // delegation}}public class Main {
     // to the outside world it looks like Printer actually prints.
     public static void main(String[] args) {
         Printer printer = newPrinter(); printer.print(); }}Copy the code

RealPrinter is an object that actually executes logic, called a delegate; Printer is the object that accepts the request, called delegator.

Classes implement interfaces through proxies (implement inheritance)

The delegate pattern has proven to be a good alternative to implementing inheritance. In Kotlin, a class can implement an interface by delegating all its public members to a specified object. With the example above, step by step look at how classes implement interfaces through proxies.

First, convert the above design pattern to Kotlin code:

 class RealPrinter {
     fun print(a){... }}class Printer {
     val p = RealPrinter()
     fun print(a) { 
       p.print()
     } 
 }

 fun main(a) {
   	 val printer = Printer()
   	 printer.print()
 }
Copy the code

Kotlin provides the by keyword to represent the agent, converting the above code to the by keyword:

class RealPrinter {
    fun print(a) {
        print("something")}}class Printer(p: RealPrinter): RealPrinter by p // If interfaces can be delegated, they can be delegated
Copy the code

Normally this would be the case, but the compiler would report an error

Only interfaces can be delegated to
Copy the code

This means that the delegate is limited to the interface. Why only interfaces can be delegated in Kotlin? Why not abstract classes or ordinary classes?

This is because when you delegate an interface, the class still implements that interface. So for consistency, if you can delegate a class, it should work the same way. Such as:

class A(x: Int) {
  fun foo(a) = x
}

class B(val a: A) : A by a
Copy the code

Compile as follows:

class B(val a: A) : A {
  override fun foo(a) = a.foo()
}
Copy the code

This is not normal because:

  • Foo is not open and cannot be overridden

  • You need to call A constructor of A, that is, if B inherits from A, the definition of B should be

    class B(val a: A) : A(x)
    Copy the code

    Obviously, if inherited from A, no argument can be used as A construction parameter of A.

  • The third point is the equals and hashCode methods. Are they delegates?

In summary, Kotlin limits the proxy of a class to an interface. So to implement the above example with the by keyword, we first need to define an interface:

interface Print {
    fun print(a)
}

class RealPrinter() : Base {
    override fun print(a){... }}class Printer(p: RealPrinter) : Print by p

fun main(a) {
    val p = RealPrinter()
    Printer(p).print()
}
Copy the code

For the above code, a Print interface is defined to act as the proxy interface. Its implementation class is RealPrinter; Specify Printer member P as delegate by the by keyword. In a real call, we create an object of type RealPrinter that actually handles the request and then call print via the Delegator object — Printer instance that accepts the request.

If you override print in Printer (delegate class), print will be performed according to print function logic in Printer, not the print logic of RealPrinter object P. Instead of calling the pirnt method of p, an instance of RealPrinter of proxy type, it calls its own print method.

Proxy properties

For attributes that can be defined as constants, we usually only need to assign them once, for example:

  • Lazy Properties: Evaluates its value only on the first access.
  • Observable Properties: Listeners receive notifications about changes to this property.
  • Strong reference type objects stored in a Map, rather than individual fields for each property.

To cover the above scenario, Kotlin provides the proxy attribute:

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

The syntax rules are:

 val/var <property name>: <Type> by <expression>
Copy the code

The expression after BY is a proxy implementation expression, and the getters and setters for this property are delegated to the getValue()/setValue() method implementation of expression. That is, the getValue()/setValue() method of expression is actually called.

The getValue()/setValue() methods are generally:

 operator fun getValue(thisRef: Any? , property:KProperty< * >): Type
 
 operator fun setValue(thisRef: Any? , property:KProperty<*>, value: Type)
Copy the code

Property brokers do not need to implement an interface, but they do need to provide a getValue function (and a setValue function).

The proxy class for the property P in Example above is implemented as follows:

import kotlin.reflect.KProperty

class Delegate {
  	// Parameters and modifiers are implemented by default
    operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")}}Copy the code

Delegate’s getValue function is called when you access the Delegate property P of a Delegate object. Its first argument is the object from which you read P, and its second argument contains a description of the property P itself.

A proxy implementation provided by the Kotlin Standard Library

The Kotlin Standard Library provides several factory methods for common agents.

Lazy properties

Lazy () is a function that takes a lambda expression argument and returns an instance of lazy

, which can be used as a delegate to implement the lazy attribute. The first call to GET () executes a lambda expression through lazy() and logs the result. Subsequent calls to get() directly return the result of the first record.

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(a) {
    println(lazyValue)
    println(lazyValue)
}
Copy the code

By default, the lazy attribute is synchronous: the value of the attribute is evaluated in only one thread, and all threads observe the same value.

If the synchronization of the initialization delegate does not need to allow multiple threads to execute simultaneously, useLazyThreadSafetyMode.PUBLICATIONPassed as a parameter tolazy().

You can use this if you are sure that the initialization will always occur in the same thread as the one you are using the property onLazyThreadSafetyMode.NONE. It does not incur any thread-safety guarantees and associated overhead.

Observables properties

Delegates.Observable () has two parameters, the initial value and the block that listens for changes.

inline fun <T> observable(
    initialValue: T.crossinline onChange: (property: KProperty< * >,oldValue: T.newValue: T) - >Unit
): ReadWriteProperty<Any? , T>Copy the code

OnChange is called each time a property change is received and takes three parameters

  • Property: description of the KProperty
  • OldValue: indicates the oldValue
  • NewValue: indicates the newValue
import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")}}fun main(a) {
    val user = User()
    user.name = "first"
    user.name = "second"
}
Copy the code

If you want to intercept callbacks and handle them logically, use vetoable() instead of Observable (). The processing logic passed to vetoable will be invoked before the new property value is assigned.

Proxy to another property

A property can delegate its getters and setters to another property, and this delegate can be used for top-level and class properties (members and extensions). The proxy property can be:

  • A top-level attribute
  • A member or extended property of the same class
  • A member or extended property of another class

Delegate one property to another, using the :: modifier in the delegate name, such as this::delegate.

// Top-level attributes
var topLevelInt: Int = 0
/ / the proxy class
class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int.val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt

    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}

// Proxy to another property
var MyClass.extDelegated: Int by ::topLevelInt
Copy the code

This can be useful, for example, when you want to rename properties in a backward-compatible way: introduce new properties, annotate old properties with @deprecated annotations, and delegate their implementation.

Store attributes in a Map

class User(valmap: Map<String, Any? >) {val name: String by map
    val age: Int by map
}

fun main(a) {
    val user = User(mapOf(
        "name" to "John Doe"."age"  to 25
    ))
    println(user.name) // Prints "John Doe"
    println(user.age)  // Prints 25
}
Copy the code

This is a simple example where the name and age of the User can be obtained from the map by proxying properties to the map.

If there is no key in the map with the same attribute name, an exception will be thrown:

Exception in thread "main" java.util.NoSuchElementException: Key age is missing in the map.
Copy the code

Temporary proxy attribute

You can declare a temporary property as a proxy property, for example, you can create a local lazy property:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}
Copy the code

The value of the memoizedFoo variable is only evaluated on the first access. If someCondition is false, this property is never evaluated.

Property broker requirements

For a read-only property, a delegate should provide an operator function called getValue() with the following parameters:

  • thisRefMust be the same as the type of the property owner or its parent type.
  • propertyThat must beKProperty<*>Type or its parent type.

Return value: getValue() must return the same type (or subtype) as the proxied property.

class Resource

class Owner {
    val valResource: Resource by ResourceDelegate()
}

class ResourceDelegate {
    operator fun getValue(thisRef: Owner, property: KProperty< * >): Resource {
        return Resource()
    }
}
Copy the code

Similarly, the setValue() function also takes the following arguments:

  • thisRefMust be the same as the type of the property owner or its parent type.
  • propertyThat must beKProperty<*>Type or its parent type.
  • value: must have the same type as the proxied property (or its supertype).
class Resource

class Owner {
    var varResource: Resource by ResourceDelegate()
}

class ResourceDelegate(private var resource: Resource = Resource()) {
    operator fun getValue(thisRef: Owner, property: KProperty< * >): Resource {
        return resource
    }
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?). {
        if (value is Resource) {
            resource = value
        }
    }
}
Copy the code

The getValue()/setValue() functions can be provided as member functions or extension functions of the delegate class. Extension functions are handy when you need to delegate properties to objects that do not initially provide these capabilities. Both functions need to be marked with the operator keyword.

Anonymous object proxy

You can use the ReadOnlyProperty and ReadWriteProperty interfaces in the Kotlin library to create delegates as anonymous objects without creating new classes. They provide the required methods:

  • GetValue () is declared in ReadOnlyProperty;

  • ReadWriteProperty extends this and adds setValue().

Because you’re extending the relationship, you can also pass ReadWriteProperty when you need ReadOnlyProperty.

fun resourceDelegate(a): ReadWriteProperty<Any? .Int> =
    object: ReadWriteProperty<Any? .Int> {
        var curValue = 0
        override fun getValue(thisRef: Any? , property:KProperty< * >): Int = curValue
        override fun setValue(thisRef: Any? , property:KProperty<*>, value: Int) {
            curValue = value
        }
    }

val readOnly: Int by resourceDelegate()  // ReadWriteProperty as val
var readWrite: Int by resourceDelegate()
Copy the code

Transformation rules for proxy properties (low-level implementation)

Underneath, the Kotlin compiler generates a secondary property for each delegate property and delegates to it. For example, for the property prop, it generates the hidden property prop$delegate, and the accessor’s code simply delegates to this additional property:

class C {
    var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
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)
}
Copy the code

The Kotlin compiler provides all the necessary information about prop in the parameters:

  • The first argument, this, refers to an instance of the external class C,
  • This :: Prop is a reflection object of type KProperty that describes the prop itself.

Proxy-to-another property transformation rule (underlying implementation)

When delegated to another property, the Kotlin compiler immediately generates access to the referenced property. This means that the compiler does not generate the field Prop $Delegate. This optimization helps save memory.

class C<Type> {
    private var impl: Type = ...
    var prop: Type by ::impl
}
Copy the code

The property accessor of the prop variable calls the IMPL variable directly, skipping the getValue and setValue operators of the delegate property, so there is no need for the KProperty reference object.

For the above code, the compiler generates the following code:

class C<Type> {
    private var impl: Type = ...

    var prop: Type
        get() = impl
        set(value) {
            impl = value
        }

    fun getProp$delegate(a): Type = impl // This method is needed only for reflection
}
Copy the code

Provide a delegate

You can extend the logic used to create objects that implement delegate properties by defining an operator function called provideDelegate.

If the object used to the right of BY defines provideDelegate as a member function or extension function, that function is called to create the property delegate instance.

One possible use case for provideDelegate is to check for consistency of properties at initialization.

For example, to check the property name before binding, write:

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty< * >): T { ... }}class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty< * >): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String){... }}class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}
Copy the code

The provideDelegate argument is the same as getValue:

  • thisRef
  • property

The provideDelegate method is called for each property during the creation of the MyUI instance, and the necessary validation is performed immediately.

Without this ability to intercept the binding between an attribute and its delegate, you would have to explicitly pass the attribute name to achieve the same functionality, which is not very convenient:

// Checking the property name without "provideDelegate" functionality
class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")}fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
    checkProperty(this, propertyName)
    // create delegate
}
Copy the code

In the generated code, the provideDelegate method is called to initialize the auxiliary Prop $delegate property. Compare the generated code for the property declaration Val Prop: Type by MyDelegate() with the generated code above (when the provideDelegate method does not exist) :

class C {
    var prop: Type by MyDelegate()
}

// this code is generated by the compiler
// when the 'provideDelegate' function is available:
class C {
    // calling "provideDelegate" to create the additional "delegate" property
    private val prop$delegate = MyDelegate().provideDelegate(this.this::prop)
    var prop: Type
        get() = prop$delegate.getValue(this.this::prop)
        set(value: Type) = prop$delegate.setValue(this.this::prop, value)
}
Copy the code

Note that the provideDelegate method only affects the creation of auxiliary properties, not the code generated for getters or setters.

Using the PropertyDelegateProvider interface in the standard library, you can create a delegate without creating a new class.

valprovider = PropertyDelegateProvider { thisRef: Any? , property -> ReadOnlyProperty<Any? .Int> {_, property -> 42}}val delegate: Int by provider
Copy the code