Delegates in Kotlin

KotlinNew syntax introduced in --delegate. Delegation is a design pattern. Its basic idea is: the operation object will not handle a certain piece of logic itself, but will delegate the work to another auxiliary object for processing. For example, calling a method Aof a class methodAis actually the execution Bof the class behind it.methodB

KotlinThe delegation function is divided into two types: class delegation and delegation property.

The core idea of ​​class delegation is to delegate the concrete implementation of a class to another class to complete. For example Set, the data it stores is out of order and cannot store duplicate data. SetIs an interface, if you want to use it, you need to use its specific implementation class, for example HashSet. With the help of the delegation model, you can easily implement your own implementation class. For example, define one here MySetand let it implement Setthe interface. The code is as follows:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
    
    
  
  override val size: Int
  		get() = helperSet.size
  
  override fun contains(element: T) = helperSet.contains(element)
  
  override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
  
  override fun isEmpty() = helperSet.isEmpty()
  
  override fun iterator() = helperSet.iterator()
  
}

It can be seen that a parameter MySetis received in the constructor , which is equivalent to an auxiliary object. HashSetThen, in Setthe implementation of all the methods of the interface, they did not implement their own implementation, but called the corresponding method implementation in the auxiliary object, which is actually a delegation mode.

Well, since they are all implemented by calling the method of the auxiliary object, it is better to use the auxiliary object directly. It is true to say so, but if we just let most of the method implementations call the methods in the auxiliary object, and a small number of method implementations are rewritten by ourselves, or even add some of our own unique methods, then it will become a brand MySetnew Data structure classes, this is the meaning of the delegation pattern.

But this way of writing also has certain disadvantages. It’s okay if there are fewer methods to be implemented in the interface. If there are dozens or even hundreds of methods, each of them will call the corresponding method in the auxiliary object to implement it. I cried. So is there any solution to this problem? There is really no such thing in Java, but Kotlinit can be solved by the function of class delegation in .

KotlinThe keywords used in the delegate are that byyou only need to use the keyword after the interface declaration by, and then connect the delegated auxiliary object, you can save a lot of template code written before, as shown below:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by heplerSet {
    
    
  
}

The effect of these two pieces of code is exactly the same, but after using the function of class delegation, the code is obviously simplified too much. In addition, if we want to reimplement a certain method, we only need to rewrite that method separately, and other methods can still enjoy the convenience brought by class delegation, as shown below:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
    
    
  
    fun helloWorld() = println("Hello World")
  
    override fun isEmpty() = false
}

A new method is added here helloWorld(), and the method is rewritten isEmpty()to make it return forever false. This is of course a wrong approach, here is just for demonstration. Now MySetit has become a brand new data structure class, not only will it never be empty, but it can also print helloWorld(). As for Setthe functions in other interfaces, it is HashSetconsistent with . This is Kotlinwhat class delegates can do.

The core idea of ​​class delegation is to delegate the specific implementation of a class to another class, and the core idea of ​​delegated attributes is to delegate the specific implementation of an attribute (field) to another class. The syntax structure of delegated properties is as follows:

class MyClass {
    
    
  var p by Delegate()
}

It can be seen that bykeywords are used to connect pthe properties on the left and Delegatethe instances on the right. This way of writing means that pthe specific implementation of the properties is entrusted to Delegatethe class to complete. When the property is called, the method of the class pis automatically called , and when the property is assigned a value, the method of the class is automatically called .DelegategetValue()pDelegatesetValue()

Therefore, it is Delegatenecessary to implement the class concretely. The code is as follows:

class Delegate {
    
    
  var propValue: Any? = null
  
  operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
    
    
    return propValue
  }
  
  operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
    
    
    propValue = value
  }   
}

This is a standard code implementation template. In Delegatethe class, we must implement getValue()and setValue()these two methods, and must use operatorkeywords to declare.

getValue()The method needs to receive two parameters: the first parameter is used to declare in which class the delegate function of Delegatethis class can be used, which is written here MyClassto indicate that it can only MyClassbe used in the class; the second parameter KProperty<*>is Kotlinan attribute operation class in the class, which can be used in Get the values ​​related to various attributes, which is not needed in the current scenario, but must be declared on the method parameters. In addition, <*>this generic way of writing means that you don't know or don't care about the specific type of the generic, just to compile it through the grammar, which is somewhat similar to the way of writing Javain <?>. As for the return value, it can be declared as any type, just write it according to the specific implementation logic. The above code is just an example.

setValue()The method is similar, except that it takes 3a parameter. The first two parameters getValue()are the same as the method, and the last parameter indicates the value to be assigned to the delegate property. The type of this parameter must be getValue()consistent with the type of the method return value.

The workflow of the entire delegated property is implemented in this way. Now when we assign a value MyClassto the property, the method of the class pwill be called , and when the value of the property is obtained, the method of the class will be called .DelegatesetValue()MyClasspDelegategetValue()

However, there is actually a situation where you don't need to Delegateimplement setValue()the method in the class, that is, the properties MyClassin the class pare valdeclared using keywords. This is also easy to understand. If pthe attribute is valdeclared using keywords, it means that pthe attribute cannot be reassigned after initialization, so there is no need to implement setValue()the method, just implement getValue()the method.

KotlinbyThe effect of delegation can be realized through keywords. For example by lazy { }, it is actually the delayed initialization syntax implemented by delegation. Here's how it's used:

val laziness: String by lazy {
    
     // by lazy实现延迟初化效果
  println("I will hava a value")
  "I am a lazy-initialized string"
}

A lazy loading technique is used here to put the code that you want to delay execution into by lazythe code block, so that the code in the code block will not be executed at the beginning, only when the lazinessvariable is called for the first time, the code in the code block code will be executed.

After learning Kotlinthe delegate function of , you can by lazydecrypt the working principle of , and its basic grammatical structure is as follows:

val p by lazy {
    
     ... }

by lazyIt is not a keyword linked together, only bya Kotlinkeyword in is, lazywhich is just a higher-order function here. lazyAn object will be created and returned in the function . DelegateWhen we call pthe property, we actually call the method Delegateof the object getValue(), and then the expression passed in by the function getValue(will be called in the method , so that the code in the expression can be executed , and the value obtained after calling the property is the return value of the last line of code in the expression.lazyLambdapLambda

From this point of view, Kotlinthe lazy loading technology is not so mysterious. After mastering its implementation principle, we can also implement a lazyfunction of our own. Create a new Later.ktfile and write the following code:

class Later<T>(val block: () -> T) {
    
     }

First, a class is defined Laterand designated as a generic class. LaterThe constructor accepts a function type parameter, which does not receive any parameters, and the return value type is the Latergeneric type specified by the class.

Then Laterimplement getValue()the method in the class, the code is as follows:

class Later<T>(val block: () -> T) {
    
    
    var value: Any? = null
    operator fun getValue(any: Any?, prop: KProperty<*>): T {
    
    
        if (value == null) {
    
    
            value = block()
        }
        return value as T
    }
}

Here, getValue()the first parameter of the method is specified as Any?a type, indicating that the desired Laterdelegation function can be used in all classes. Then use a valuevariable to cache the value, if valueit is empty, call the function type parameter passed in the constructor to get the value, otherwise return directly. Since lazy loading technology does not assign values ​​to attributes, there is no need to implement setValue()methods here.

The code is written here, and the function of the delegate property has been completed. Although we can use it immediately, in order to make its usage more like lazya function, it is better to define a top-level function. Later.ktThis function can be written directly in the file, but it must be defined Lateroutside the class, because only functions that are not defined in any class are top-level functions. The code looks like this:

fun <T> later(block: () -> T) = Later(block)

We also define this top-level function as a generic function, and it also receives a function type parameter. The function of this top-level function is very simple: create Lateran instance of the class, and pass the received function type parameter to Laterthe constructor of the class.

Now, our self-written laterlazy loading function is complete, you can directly use it to replace the previous lazyfunction, as shown below:

val uriMatcher by later {
    
    
    val matcher = UriMatcher(UriMatcher.NO_MATCH)
    matcher.addURI(authority, "book", bookDir)
    matcher.addURI(authority, "book/#", bookItem)
    matcher.addURI(authority, "category", categoryDir)
    matcher.addURI(authority, "category/#", categoryItem)
    matcher
}

But how can we verify laterwhether the lazy loading function of the function has taken effect? Here I have a very simple and convenient verification method, which is written as follows:

val p by later {
    
    
    Log.d("TAG", "run codes inside later block")
    "test later"
}

As you can see, we laterprinted a line of log in the code block of the function. Put this code in either Activityand call pthe property on the button's click event.

You will find that the log line in the function will not be printed Activitywhen it is started . laterOnly when you click the button for the first time, the log will be printed, indicating that the code in the code block was executed successfully. And when you click the button again, the log will not be printed again, because the code in the code block will only be executed once.

In this way, you can verify whether the lazy loading function has taken effect, and you can test it yourself.

In addition, it must be noted that although we have written a lazy loading function of our own, for the sake of simplicity, the basic implementation principle of the function is only roughly restored here, and lazysome aspects such as synchronization and null value handling are not implemented very rigorously. . Therefore, in a formal project, using the Kotlinbuilt-in lazyfunction is the best choice.

Requirements can be implemented through delegation instead of multiple inheritance:

interface CanFly {
    
    
    fun fly()
}

interface CanEat {
    
    
    fun eat()
}

open class Flyer : CanFly {
    
    
    override fun fly() {
    
    
        println("I can fly")
    }
}

open class Animal : CanEat {
    
    
    override fun eat() {
    
    
        println("I can eat")
    }
}

class Bird(flyer: Flyer, animal: Animal) : CanFly by flyer, CanEat by animal {
    
     }

fun main() {
    
    
    val flyer = Flyer()
    val animal = Animal()
    val b = Bird(flyer, animal)
    b.fly()
    b.eat()
}

Some people may have doubts: First, why is the delegation method so similar to interface implementation of multiple inheritance, and it does not seem to be much simpler. Second, this method seems to be very similar to composition, so what advantages does it have? There are two main points:

  • As mentioned earlier, the interface is stateless, so even if it provides a default method implementation, it is very simple and cannot implement complex logic, and it is not recommended to implement complex method logic in the interface. We can use the method of delegation above, although it is also an interface delegation, but it uses a specific class to implement method logic, which can have more powerful capabilities;
  • Assuming that the class we need to inherit is A, the delegate object is B, C, when we call it specifically, it is not like a combination A.B.method, but can be called directly A.method, which can better express the ability Ato have this method, and is more intuitive, although it is also through the delegate object. Execute specific method logic;

Guess you like

Origin blog.csdn.net/xingyu19911016/article/details/126414296