Use several simple cases to help you quickly understand kotlin delegation

kotlin entrustment

On the introduction of delegation, I will explain the most commonly used ways to implement delegation with several examples of my own implementation. The description will be very clear and easy to understand.

In particular, the Lazy initialization method of the inherited Lazy class in the ViewModel extension library in android will be simply simulated to help you better understand

Using delegates to implement inheritance

Delegate using the by keyword

  1. Code description


When calling the drive method of BrokenTaxi, the method call will be forwarded to the drive method of Taxi passed in by the formal parameter

The compiler will give priority to the replication method. For example, if our BrokenTaxi replicates the startupEngin method, it will not call the startupEngine method of Taxi

  1. Output results

Because BrokenTaxi indicates a broken taxi, the engine cannot be started. You need to rewrite startupEngine. When the user wants to start the engine, he will tell him that it cannot be started

Standard entrustment

Delay attribute

Initialize on first visit

Lazy will accept a lambda and return a function of a lazy instance. The returned instance can be used as a delegate to implement the delay attribute. When the attribute is used for the first time, the return value of the lambda expression will be passed to the attribute

By default, the initialization of the delay attribute is carried out in the synchronization lock, and this value can be observed by all threads.

If you are sure that initialization will always occur on the same thread as the property, you can use lazythreadsafetymode None mode: it will not have any thread safety guarantee and related overhead. The implementation code is as follows:

 val laze2 :String by lazy(LazyThreadSafetyMode.NONE){
        "Android"
    }

Observable attribute

Listeners are notified of this property change

Observable attributes can be implemented in two ways: observable and vetoable

The observable method can receive a callback when the attribute changes;

The vetoable method can also receive callbacks, but when using vetoable, we can intercept according to conditions to decide whether to update the value of the attribute;

Create in observable mode


When we change the value of field, the lambda expression will be called back

Create using vetoable

It is also important to create observable attributes in vetoable mode. We can intercept them in lambda expression and decide whether to update the value of the attribute

Store attributes in mapping

We can use map mapping to delegate attributes. The key of map is the name of the delegated attribute, and the value of map is the value of the delegated attribute

Local delegate properties

Attribute delegation requirements

Attribute delegation should have been mentioned first, because I understood it very slowly when I first learned about delegation, so let's take a look at the previous things. It may be easier to accept attribute delegation here.

Property delegate must provide a getValue method. If it is a var property, it must also provide a setValue method

  1. Explanation of the basic principle of attribute delegation

  1. Property changes and method calls in delegate classes

For attribute delegation, if the delegated attribute field is set to var, the delegate class ResourceDelete needs to implement getValue and setValue methods at the same time.

Then I have the following questions:

1. Is the getValue method called every time we use the field attribute reference (answer: yes)
2. Is the setValue method called every time we change the field property reference (answer: yes)

There are too many codes. The screenshot can't be put down. Go to the code

After running the code, the verification of the above two conclusions will be released

class EnTrustObject {
    var field: Resource by ResourceDelete()
}
class ResourceDelete {
    var resource: Resource = Resource("Initial object")
    operator fun getValue(enTrustObject: EnTrustObject, property: KProperty<*>): Resource {
        println("Called getValue method")
        return resource
    }

    operator fun setValue(enTrustObject: EnTrustObject, property: KProperty<*>, resource: Resource) {
        this.resource = resource
        println("Called setValue method")
    }

}
class Resource(val value:String) {
    fun print() {
        println("$value Android print resources")
    }
}

fun main() {
    EnTrustObject().apply {
        field.print()
        field= Resource("Changed object")
        field.print()
       field.print()
    }
}

Delegate using the Lazy class

Lazy's description

kotlin provides us with a Lazy class to help us realize more complex Lazy loading. I have translated the description of Lazy into screenshots as follows:

In other words, Lazy can help us do such a thing. The value of the attribute we want to obtain is stored in value, and the isInitialized method is provided to judge whether the attribute has been initialized.

If it has been initialized, the value will not be reset when we get the property with the following code.

private val engine: Engine by engine()

Lazy loading using lazy

We have made such an implementation:

Create a Car class. The Car class holds an engine class. The engine is initialized by lazy loading. See the code:

fun main() {
    Car().apply {
        myEngine()
        myEngine()
        myEngine()
    }
}

/**
 * Declare automobile class
 */
class Car {
    private val engine: Engine by engine()//Initializing the engine using extension functions
    fun myEngine() {
        engine.showIntroduce()
    }
}

/**
 * Declare engine class
 */
class Engine(private val describe: String) {

    fun showIntroduce() {
        println(describe)
    }
}

/**
 * Create an extension function to get the engine,
 */
fun Car.engine(): LazyEngine {
    return LazyEngine()
}

/**
 * Engine initialization class, Lazy class, generic engine class
 */
class LazyEngine : Lazy<Engine> {
    private var cache: Engine? = null

    /**
     * If the engine cache is not empty, it indicates that it has been initialized, otherwise it will not be initialized
     */
    override fun isInitialized(): Boolean {
        return cache != null
    }

    /**
     * Get engine value
     */
    override val value: Engine
        get() {
            val newValue = cache
            return if (newValue == null) {//When the engine is not created, the cache value will be returned if any
                Engine("Narrator(Android): Chang'an Mazda, twin turbocharged engine")
            } else {
                cache!!
            }
        }
}

Code execution result:

For Lazy loading implemented in Lazy mode, if you want to learn more complex application scenarios, it is recommended that you read the source code of ViewModel initialization method in android ViewModel extension library, and you will receive a lot of goods

Finally, I brought myself a salt:

Welcome to learn more about my official account, "Ann Ann an Android".

Keywords: Android kotlin

Added by fantasticham on Mon, 24 Jan 2022 11:38:17 +0200