preface

When it comes to delegation, it’s easy to think of the delegate pattern, which is essentially delegating operations to another party to perform. In Java, delegation is only limited to delegate implementation between classes. In Kotlin, in addition to class delegation, it also supports attribute delegation. In addition to the delegation support provided by the standard library, we can use delegation more gracefully in the application.

Commissioned by class

Xiao CAI is a programmer, his job is to change the bug, one day xiao CAI met the big devil of the bug, with the ability of xiao CAI, so xiao CAI got the work of the big devil of the bug entrusted to colleagues Xiao Zhang, Xiao Zhang is worthy of being the big guy, easily got the big devil of the bug.

If the above story is expressed in code, first of all, Xiao Zhang and Xiao CAI have the same work — bug fixing, can abstract out an interface.

interface Programmer {
    fun fixBug(a)
}
Copy the code

Zhang, as the client, was instrumental in getting rid of the Bug Demon king

class ProgrammerZhang : Programmer {
    override fun fixBug(a) {
        println("xiao zhang fix the bug")}}Copy the code

And small dish serves as trustee, the task that solves bug big demon king is entrusted to small zhang

class ProgrammerCai(programmer: Programmer) : Programmer by programmer
​
//output --> xiao zhang fix the bug
Copy the code

This parameter is passed into the interface, you can change the above story, there are three colleagues can solve the big devil bug, but I don’t know who is available, then the delegate may be one of the three, but they all have one thing in common — they can fix the bug(all implement Programmer interface). It makes sense to pass in interface parameters here.

Kotlin uses the by keyword to represent the delegate. Above xiao CAI by the keyword to solve the problem of the big devil delegated to Xiao Zhang. So here’s the class delegate, even though the delegate object implements the Programmer interface, with the by keyword, it ends up calling the fixBug() method implemented by the principal.

But if Xiao CAI suddenly gets inspired and finds a solution, he doesn’t need to entrust xiao Zhang to solve it. That is, if the fixBug() method is overridden, the principal implementation will not be called in the end.

class ProgrammerCai(programmer: Programmer) : Programmer by programmer {
    override fun fixBug(a) {
        println("xiao cai fix the bug")}}//output --> xiao cai fix the bug
Copy the code

Attribute to entrust

Although xiao CAI is very busy at work, he still insists on maintaining his hair every week, but the maintenance work is entrusted to barber Tony.

class ProgrammerCai{
   var hair : Hair by TonyDelegate() 
}
Copy the code

The class ProgrammerCai declares the hair property, which is delegated to TonyDelegate() by the by keyword. This is the full syntax for attribute delegates val/var < attribute name >: < type > by < expression >, with the keyword by followed by the delegate. To use the attribute delegate syntax properly, a delegate needs to implement the getValue/setValue methods of the delegate attribute, as in TonyDelegate() :

class TonyDelegate {
​
    private var newValue: Hair = Hair()
​
    operator fun getValue(thisRef: Any? , property:KProperty< * >): Hair {
        println("Tony finished the maintenance.")
        return newValue
    }
​
    operator fun setValue(thisRef: Any? , property:KProperty<*>, value: Hair) {
        println("Give $value to Tony for maintenance."
        newValue = value
    }
}
Copy the code
  • thisRef– must be connected toProperty ownerType (for extended attributes – the type being extended) is the same or its supertype.
  • property— Must be a typeKProperty<*>Or its supertype.
  • value– Must be the same as the property type (or its supertype).

What if you can’t remember the parameters when implementing a delegate function like TonyDelegate? It doesn’t matter, also provides two interfaces in Kotlin ReadOnlyProperty/ReadWriteProperty corresponding val/var modified properties.

class TomDelegate : ReadWriteProperty<ProgrammerCai,Hair>{
    override fun getValue(thisRef: ProgrammerCai, property: KProperty< * >): Hair {
       
    }
​
    override fun setValue(thisRef: ProgrammerCai, property: KProperty<*>, value: Hair){}}Copy the code

If Tony hires an assistant one day, the assistant’s job is to verify customer information for Tony and then delegate the customer to Tony. How do you deal with that?

You can do this by declaring an extension method where the parameter passed in is the attribute name.

private fun ProgrammerCai.assistantDelegate(name: String): ReadWriteProperty<ProgrammerCai, Hair> {
    checkData(name)
    return TonyDelegate()
}
​
class ProgrammerCai {
     var hair: Hair by assistantDelegate("hair")}Copy the code

But one of the downsides is passing in the property name every time. Is there a cleaner way to do that?

Yes, we can modify the code in this case by defining the provideDelegate operator

class AssistantDelegate {
    operator fun provideDelegate(
        thisRef: ProgrammerCai,
        prop: KProperty< * >): ReadWriteProperty<ProgrammerCai, Hair> {
        checkData(prop.name)
        return TonyDelegate()
    }
}
​
 class ProgrammerCai {
     var hair: Hair by AssistantDelegate()
 }
​
Copy the code

So we don’t have to pass in the name of the property every time.

Taking a closer look at the provideDelegate, which actually intercepts the property and its delegate binding, we can apply it according to the situation. There are also restrictions on the use of the provideDelegate and its arguments need to be the same as the getValue() argument.

Library delegate

Kotlin’s optimization and encapsulation of delegates is excellent, and the Kotlin standard library currently provides factory methods for several useful delegates, respectively

  • Lazy properties: its value is only evaluated on first access;
  • Observable Properties: Listeners receive notifications about changes to this property.
  • Store multiple attributes in a map instead of each in a separate field.

Lazy properties

The value of the delay attribute can only be evaluated on first access, and its standard use is the lazy method, which takes a lambda expression and returns a lazy object.

val lazyValue:Int by lazy {
      0
}
Copy the code

It’s easy to use, but if you dig a little deeper into the lazy method, you’ll see that it can be declared in two ways

1 / / method
public actual fun <T> lazy(initializer: () -> T): Lazy<T>
​
2 / / method
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>
Copy the code

Method 2 has one more enumeration parameter than method 1, mode, to specify the mode to implement.

public enum class LazyThreadSafetyMode {
    SYNCHRONIZED,
    PUBLICATION,
    NONE,
}
Copy the code
  • SYNCHRONIZED – Use locks to ensure thread safety
  • PUBLICATION — the initializer function can be called multiple times when an uninitialized instance value of [Lazy] is concurrently accessed, but only the first return value will be used as the value of the [Lazy] instance
  • NONE — Non-thread-safe, does not handle any concurrency. Not recommended unless you can guarantee that initialization will not be called by multiple threads

Method 1 uses a thread-safe implementation, although it does not take an enumeration argument. If you want to continue to see lazy, keep going

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    // Attribute initializer block
    private var initializer: (() -> T)? = initializer
    // Specify the attribute value
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    / / object lock
    private vallock = lock ? :this
    
    override val value: T
        get() {
            // If already initialized, do not apply for lock again to reduce overhead
            val _v1 = _value
            if(_v1 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            // uninitialized, synchronized ensures thread synchronization
            return synchronized(lock) {
                val _v2 = _value
                // Make sure to initialize only once
                if(_v2 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    valtypedValue = initializer!! () _value = typedValue initializer =null
                    typedValue
                }
            }
        }
       ...
}
Copy the code

Synchronized is used to ensure thread synchronization, and two checks ensure initialization only once, similar to the singleton mode implemented by double checklock.

One thing to note is that if a property is lazy initialized using by lazy{}, then the property should not be var mutable. Some indication of this can be seen with the return value Lazy

.

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any? , property:KProperty< * >): T = value
Copy the code

Lazy

defines only the getValue method, so attributes declared as var are not supported.

Observable Properties (Observable Properties)

Delegates. Observale () is the method of monitoring observable property after assignment, contrary to the method called Delegates. Vetoable () is the judgment before assignment, if the condition is met, the value will be assigned.

class ProgrammerCai{
    var name:String by Delegates.observable("xiao cai"){
        property, oldValue, newValue ->
        // Output after assignment
        println("$oldValue->$newValue")}var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
            // If newValue is greater than 30, no value will be assigned
            newValue < 30}}Copy the code

ObservableProperty, which implements the ReadWriteProperty interface, can be used as a delegate for the variable property of var. Internally, two additional methods are declared beforeChange() and afterChange(), one of which is used by vetoable and Observable, respectively.

public override fun setValue(thisRef: Any? , property:KProperty<*>, value: V){
    val oldValue = this.value
    //vetoable uses beforChange()
    if(! beforeChange(property, oldValue, value)) {return
    }
    this.value = value
    AfterChange () is an afterChange observable.
    afterChange(property, oldValue, value)
}
Copy the code

Delegate to the mapping object

class ProgrammerCai(map: Map<String, Any? >) {val name: String by map
    val age: Int by map
}
Copy the code

It’s also easier to use

val programmer = ProgrammerCai(
    mapOf(
        "name" to "xiao cai"."age" to 18))Copy the code

It is easy to understand from the above code that this works by taking the values of name and age from the map and assigning them. The principle is very simple, but it is rarely used in daily work, the use of the scene is relatively rare, may be used in Json parsing. In addition, mutable attributes can be supported if the map type is changed to MutableMap.

conclusion

Kotlin has good support for delegates, which can be flexibly used in practical applications. For example, ViewModel initialization can be easily initialized in an Activity by viewModels

(). The above content is intended to do a share, if there are mistakes welcome to correct.