1. What is a delegate?

Delegate, also known as the delegate pattern, is one of the 23 classical design patterns, also known as the proxy pattern. In the delegate pattern, two objects participate in the processing of the same request, and the object receiving the request delegates the request to another object. The delegate pattern is a technique, and several other design patterns such as policy pattern, state pattern, and visitor pattern are scenario-specific applications of the delegate pattern.

In the delegate pattern, there are three roles, constraint, delegate object, and delegate object.

  • Constraints: Constraints are interfaces or abstract classes that define common business types that need to be proxied

  • Delegated object: the concrete executor of the business logic

  • Delegate object: Responsible for the application of real roles and delegating the business defined by constraints to specific delegate objects.

2. Specific scenarios of delegation

In the last section, we looked at the definition of a delegate and the roles it contains. How can we use it? Let’s look at a practical example.

Many young people now love to play games, whether it’s Chicken, Honor of Kings or League of Legends. They all have levels: bronze -> silver -> gold -> Platinum -> Diamond -> grandmaster -> King, the higher the level, the better you are. Take League of Legends for example, most of us are in the silver and gold stage, it is very difficult to get to the diamond grandmaster level. For example, if you have been ranked for a long time, you are only a few matches away from becoming a grand master, but you can’t get there. What should you do? Easy to handle, there are a lot of games now, the commission of the game to play up to you. This is essentially a delegation pattern. How do I write the code? Take a look:

First, we define the constraint class, and we define the business we need to delegate. In this case, our business is to rank and level up. Therefore, define a constraint class (interface)IGamePlayer:

/ / constraints
interface IGamePlayer {
    // Play qualifying
    fun rank(a)
    / / upgrade
    fun upgrade(a)
}
Copy the code

In the constraint class, we define the service rank(), upgrade(), and then we define the entrusted object, namely the game enabler:

// The delegate object, the game agent in this scene
class RealGamePlayer(private val name: String): IGamePlayer{
    override fun rank(a) {
        println("$nameStart qualifying")}override fun upgrade(a) {
       println("$nameThe upgrade")}}Copy the code

As above, we define a delegate object, RealGamePlayer, which has a property name that implements our agreed business (implementing interface methods).

Next, the delegate role:

// Delegate object
class DelegateGamePlayer(private val player: IGamePlayer): IGamePlayer by player
Copy the code

We define a delegate class, DelegateGamePlayer. Now we have a lot of delegate classes, high or low, and we can always change if we find that we can’t, so we pass in the delegate object as a property of the delegate object through the constructor.

Note: In Kotlin, the delegate is decorated with the keyword BY, which is followed by the object you delegate, which can be an expression. So in this case, by Player delegates to a specific entrusted object.

Finally, take a look at the scenario test class:

// Client scenario test
fun main(a) {
    val realGamePlayer = RealGamePlayer("Zhang")
    val delegateGamePlayer = DelegateGamePlayer(realGamePlayer)
    delegateGamePlayer.rank()
    delegateGamePlayer.upgrade()
}
Copy the code

We define a game enabler named Joe, pass it to the delegate class, and then we can start the ranking and upgrading business, and finally who completes the ranking and upgrading, of course, is our delegate, that is, the game enabler — Joe.

The result is as follows:

John John starts qualifying John John is upgradedCopy the code

Summary: This is the application of delegate, let’s review its definition: Two objects participate in the same request, which is our constraint logic, so both DelegateGamePlayer and RealGamePlayer need to implement our constraint interface IGamePlayer.

3. Attribute delegate

In Kotlin, there are some common attribute types that we can implement manually every time we need them, but they are cumbersome. There is all sorts of boilerplate code, and as we know, Kotlin claims to implement zero boilerplate code. To solve these problems? The Kotlin standard provides us with delegate properties.

class Test {
    // Attribute delegate
    var prop: String by Delegate()
}
Copy the code

The syntax for delegate attributes is as follows:

Var < attribute name >: < type > by < expression >

It’s similar to the delegate we did before, except the class delegate, in this case the property delegate.

3.1 Principle of attribute delegation

In the delegate, we have a constraint role that defines the business logic of the broker. What about delegate properties? In this simplification, the propped logic is the get/set method for this property. Get /set delegates to the setValue/getValue methods of the delegated object, so the delegated class needs to provide setValue/getValue. If it is a val property, simply provide getValue. If it is a var attribute, both setValue and getValue need to be provided.

Like the Delegate class above:

class Delegate {
    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

The parameters are described as follows:

  • thisRefMust be the same as the property owner type (for extended properties, the extended type) or its supertype;
  • property— Must be a typeKProperty<*>Or its supertype.
  • valueMust be of the same type or a subtype of the property.

The tests are as follows:

fun main(a) {
    println(Test().prop)
    Test().prop = "Hello, Android tech grocery store!"
}
Copy the code

The print result is as follows:

Test@5197848c, thank you for delegating 'prop'to me! Hello, Android technology grocery store! has been assigned to'prop' in Test@17f052a3.
Copy the code
3.2 Another way to implement attribute delegation

If you want to implement a property delegate, you have to provide a getValue/setValue method, and for those of you who are a little lazy, you have to write all these complicated parameters by hand, which is a real hassle. Indeed, to solve this problem, the Kotlin library declares two ReadOnlyProperty/ReadWriteProperty interfaces with the required operator methods.

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty< * >): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty< * >): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Copy the code

The delegate class implements either of these interfaces: the val property implements ReadOnlyProperty, and the var property implements ReadOnlyProperty.

// The val attribute delegate implementation
class Delegate1: ReadOnlyProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty< * >): String {
        return "By implementing ReadOnlyProperty, name:${property.name}"}}// var delegate implementation
class Delegate2: ReadWriteProperty<Any,Int>{
    override fun getValue(thisRef: Any, property: KProperty< * >): Int {
        return  20
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
       println("The delegate attribute is:${property.name}The delegate value is:$value")}}/ / test
class Test {
    // Attribute delegate
    val d1: String by Delegate1()
    var d2: Int by Delegate2()
}
Copy the code

As shown in the code above, two property brokers are defined, both implemented through the ReadOnlyProperty/ReadWriteProperty interface.

The test code is as follows:

   val test = Test()
    println(test.d1)
    println(test.d2)
    test.d2 = 100
Copy the code

Print result:

This is achieved by implementing ReadOnlyProperty, name:d120The delegate property is: d2 The delegate value is:100
Copy the code

As you can see, this has the same effect as manually implementing setValue/getValue, but it makes writing code much easier.

4. Several delegates are provided in the Kotlin library

Several delegates are provided in the Kotlin library, such as:

  • 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.
4.1 The delay attribute lazy

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

that can be used as a delegate to implement the delay attribute: The first call to get() executes the lambda expression passed to lazy() and records the result, and subsequent calls to get() simply return the result of the record.

val lazyProp: String by lazy {
    println("Hello, the first call will execute me!")
    "Sigo!
}

// Print lazyProp 3 times to see the result
fun main(a) {
    println(lazyProp)
    println(lazyProp)
    println(lazyProp)
}
Copy the code

The print result is as follows:

Hello, the first call will execute me! West elder brother! West elder brother! West elder brother!Copy the code

As you can see, only the first call executes the logic in the lambda expression, and subsequent calls only return the final value of the lambda expression.

4.1.1 Lazy is also acceptable

Lazy lazy initialization is acceptable and provides the following three parameters:

/** * Specifies how a [Lazy] instance synchronizes initialization among multiple threads. */
public enum class LazyThreadSafetyMode {

    /** * Locks are used to ensure that only a single thread can initialize the [Lazy] instance. */
    SYNCHRONIZED,

    /** * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, * but only the first returned value will be used as the value of [Lazy] instance. */
    PUBLICATION,

    /** * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread. */
    NONE,
}
Copy the code

The three parameters are described as follows:

  • LazyThreadSafetyMode. SYNCHRONIZED: adding synchronization locks, delay the lazy initialization thread-safe

  • Lazythreadsafetymode.publication: An initialized lambda expression can be called multiple times at the same time, but only the first returned value is the initialized value.

  • Lazythreadsafetymode. NONE: There is no synchronization lock. When accessing multiple threads, the value of the initialization is unknown and not thread-safe

Use as follows:

val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("Hello, the first call will execute me!")
    "Sigo!
}
Copy the code

If you specify the parameters for LazyThreadSafetyMode SYNCHRONIZED, it can be omitted, because lazy. The default is to use LazyThreadSafetyMode SYNCHRONIZED.

4.2 Observable Property

If you want to observe the changing process of a property, Delegates the property to Delegates. Observable, the prototype of observable is as follows:

public inline fun <T> observable(initialValue: T.crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any? , T> =object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }
Copy the code

Two parameters are accepted:

  • initialValueInitial value:
  • onChange: Callback handler when the property value is modified. The callback takes three parametersproperty.oldValue.newValue, respectively: the assigned attribute, the old value and the new value.

Use as follows:

var observableProp: String by Delegates.observable("Default: XXX"){
    property, oldValue, newValue ->
    println("property: $property: $oldValue -> $newValue ")}/ / test
fun main(a) {
    observableProp = "First modified value"
    observableProp = "Second modified value"
}
Copy the code

Print the following:

String: Var observableProp: Kotlin. String: Var observableProp: Kotlin. String: var observableProp: Kotlin. String: var observableProp: Kotlin. String: var observableProp: Kotlin. First change value -> Second change valueCopy the code

As you can see, every time you assign a value, you can see how the value changes.

2 vetoable function

Like an Observable, a Vetoable can observe changes in an attribute value. The difference is that a Vetoable can use processor functions to determine whether an attribute value takes effect.

For example: declare a property of type Int, vetoableProp. If the new value is greater than the old value, it takes effect, otherwise it does not.

The code is as follows:

var vetoableProp: Int by Delegates.vetoable(0){
    _, oldValue, newValue ->
    // If the new value is greater than the old value, it takes effect
    newValue > oldValue
}
Copy the code

Test code:

fun main(a) {
    println("vetoableProp=$vetoableProp")
    vetoableProp = 10
    println("vetoableProp=$vetoableProp")
    vetoableProp = 5
    println("vetoableProp=$vetoableProp")
    vetoableProp = 100
    println("vetoableProp=$vetoableProp")}Copy the code

Print the following:

vetoableProp=0
 0 -> 10 
vetoableProp=10
 10 -> 5 
vetoableProp=10
 10 -> 100 
vetoableProp=100
Copy the code

You can see that the assignment of 10 -> 5 does not take effect.

4.3 Attributes Are stored in mappings

Alternatively, you can store attribute values in a map and implement delegate attributes using the map instance itself as a delegate, for example:

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

The tests are as follows:

fun main(a) {
    val user = User(mapOf(
        "name" to "The elder brother of the west"."age"  to 25
    ))
   println("name=${user.name} age=${user.age}")}Copy the code

Print the following:

Name = west elder brother age = 25Copy the code

Implementing delegate properties using the mapping instance itself as a delegate can be used in JSON parsing, since JSON itself can be parsed into a map. However, to be honest, I haven’t found any advantages or advantages of this scenario yet. If you know, please let me know in the comments section. Thank you!

5. To summarize

Delegates play an important role in Kotlin, especially property delegates, lazy lazy initialization, and other scenarios, such as in our Android development, where property delegates encapsulate SharePreference, greatly simplifying storage and access. In our software development, we always advocate high cohesion, low coupling. Delegation, which is cohesion, reduces coupling. On the other hand, the use of delegates can also reduce a lot of repetitive boilerplate code.

Reference: www.kotlincn.net/docs/refere…

If you like my article, please follow my public account Android technology grocery store, Jian Shu or Github! Wechat official account: Android technology Grocery Store

Jane: www.jianshu.com/u/35167a70a…

GitHub:github.com/pinguo-zhou…