Entrust is a common pattern, it has nothing to do with programming language, namely themselves do entrusted to another object. Both the decorator and proxy patterns reuse behavior through delegation. Kotlin supports delegation at the language level. This article introduces Kotlin’s delegation with examples.

Kotlin’s decorator pattern

The decorator pattern has the same purpose as inheritance, to extend classes, but in a more complex way: inheritance + composition. Decorator patterns extend functionality by reusing existing types and behaviors. Detailed analysis about the decorator pattern and proxy mode can click on the use of combination of design patterns | chasing girls want to use the remote agent model.

Here is an example of the decorator pattern:

interface Accessory {
    fun name(a): String // Name of accessories
    fun cost(a): Int // Price of accessories
    fun type(a): String // Accessories category
}
Copy the code

This interface is used to describe an abstract part. A concrete part needs to implement three methods to define the part name, price, and category.

Feather, ring and earring are three specific accessories, which are implemented as follows:

class Feather: Accessory{
    override fun name(a): String = "Feather"
    override fun cost(a): Int  = 20
    override fun type(a): String  = "body accessory"
}

class Ring: Accessory{
    override fun name(a): String = "Ring"
    override fun cost(a): Int  = 30
    override fun type(a): String  = "body accessory"
}

class Earrings: Accessory{
    override fun name(a): String = "Earrings"
    override fun cost(a): Int  = 15
    override fun type(a): String  = "body accessory"
}
Copy the code

Now we need to add feather rings and feather earrings, which can be achieved according to the inheritance idea as follows:

class FeatherRing: Accessory{
    override fun name(a): String = "FeatherRing"
    override fun cost(a): Int  = 35
    override fun type(a): String  = "body accessory"
}

class FeatherEarrings: Accessory{
    override fun name(a): String = "FeatherEarrings"
    override fun cost(a): Int  = 45
    override fun type(a): String  = "body accessory"
}
Copy the code

The downside of writing this way is that you only reuse the type, not the behavior. Every time you add a new type you have to add a new subclass, which causes subclass inflation. If we use decorator mode, we can reduce one subclass:

class Feather(private var accessory: Accessory) : Accessory {
    override fun name(a): String = "Feather" + accessory.name()
    override fun cost(a): Int = 20 + accessory.cost()
    override fun type(a): String  = accessory.type()
}
Copy the code

Now Feather rings and Earrings can be expressed as Feather(Ring()) and Feather(Earrings() respectively.

The Feather application group holds an abstract component so that the behavior of the injected component can be reused. Name () and cost() append new functionality to the reusable behavior, while Type () directly delegates the implementation to accessory.

The Feather class can be further simplified using Kotlin’s delegate syntax:

class Feather(private var accessory: Accessory): Accessory by accessory {
    override fun name(a): String = "Feather" + accessory.name()
    override fun cost(a): Int = 20 + accessory.cost()
}
Copy the code

The by keyword appears after the class name, which is a class delegate, that is, you delegate the implementation of the class to an object, which must implement the same interface as the class, in this case the Accessory interface. The advantage of using BY is that it eliminates the template code, as shown above, and the implementation of the type() interface can be omitted.

Lazy initialization once

Lazy initialization is also a common pattern: delaying the initialization of an object until it is first accessed. Lazy initialization is especially valuable when initialization consumes a lot of resources.

Support attributes are an idiomatic technique for lazy initialization:

class BitmapManager {
    // The support attribute is used to store a set of bitmaps
    private var _bitmaps: List<Bitmap>? = null
    // A set of bitmaps for external access
    val bitmaps: List<Bitmap>
        get() {
            if (_bitmaps == null) {
                _bitmaps = loadBitmaps()
            }
            return_bitmaps!! }}Copy the code

The support attribute _bitmaps is private and is used to store a set of bitmaps, while another bitmaps of the same type is used to provide access to a set of bitmaps.

Only the first time you access BitMapManager. bitmaps will load the Bitmap. On the second access, the Bitmap is not reloaded, and the _bitmap is returned directly.

This is a technique used internally by Kotlin’s predefined lazy() function to kill the template code:

class BitmapManager {
    val bitmaps by lazy { loadBitmaps() }
}
Copy the code

The keyword BY appears after the property name and represents the delegate of the property, that is, the delegate of the read and write of the property to another object. The delegated object must meet certain conditions:

  1. When a property delegate is made to a read-only variable decorated by val, the delegate object must implementgetValue()Interface that defines how to get the value of a variable.
  2. When an attribute delegate is made to a var decorated read/write variable, the delegate object must implement itgetValue()andsetValue()The interface that defines how to read and write variable values.

There are three ways to implement property delegation

The return value of the lazy() method is a lazy object:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

public interface Lazy<out T> {
    public val value: T
    public fun isInitialized(a): Boolean
}
Copy the code

The Lazy class does not directly implement the getValue() method. It uses another, more flexible approach:

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

GetValue () is declared as an extension of the Lazy class. This is a unique feature of Kotlin that adds functionality to a class outside of the class. Especially useful when the original class cannot be modified.

In addition to extension functions, there are two other ways to implement the delegate class (assuming the proxy is of type String) :

class Delegate {
    operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
        return "Delegate"
    }

    operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String){}}Copy the code

This creates a new proxy class and overrides the getValue() and setValue() operators for the value and set operations, respectively, in the class with the keyword operator.

The last is as follows (assuming the agent is of type String) :

class Delegate : ReadWriteProperty<Any? , String> {
    override fun getValue(thisRef: Any? , property:KProperty< * >): String {
        return "Delegate"
    }

    override fun setValue(thisRef: Any? , property:KProperty<*>, value: String){}}Copy the code

That is, implement the getValue() and setValue() methods on the ReadWriteProperty interface.

You can then use the proxy class like this:

class Test {
    var str: String by Delegate()
}
Copy the code

The implementation behind the property delegate is as follows:

class Test {
    private delegate = Delegate()
    var str : String
        get () = delegate.getValue(this, kProperty)
        set (value: String) = delegate.setValue(this, kProperty, value)
}
Copy the code

The newly created Delegate class is stored in a supporting property Delegate, delegating the setting of the property and the implementation of the value method to the proxy class.

After the delegate, when accessing the delegate property, it is like calling the method of the proxy class:

val test = Text()
val str = test.str // Equivalent to val STR = test.delegate.getValue(test, kProperty)
val test.str = str // Equivalent to test.delegate.setValue(test, Kproperty, STR)
Copy the code

Entrust application

1. More easily obtain the transmission parameters

Delegates can hide details, especially if the details are some template code:

class TestFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        valid = arguments? .getString("id") ?: ""}}class KotlinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        valid = intent? .getStringExtra("id") ?: ""}}Copy the code

The code that gets the value passed to the Activity or Fragment is a template. You can use the delegate to hide the details:

// Create a new Extras class as the delegate class
class Extras<out T>(private val key: String, private val default: T) {
    // Overloads the value operator
    operator fun getValue(thisRef: Any, kProperty: KProperty< * >): T? =
        when (thisRef) {
            // Get the parameters passed to the Activity
            is Activity -> { thisRef.intent?.extras?.get(key) as? T ? : default }// Get the parameters passed to the Fragment
            isFragment -> { thisRef.arguments? .get(key) as? T ? : default }else -> default
        }
}
Copy the code

Then you can use the delegate like this:

class TestActivity : AppCompatActivity() {
    private val id by Extras("id"."0")}class TestFragment : Fragment() {
    private val id by Extras("id"."0")}Copy the code

2. Obtain the MAP value more easily

Some classes have not fixed attributes, but sometimes more and sometimes less, which is dynamic. For example:

class Person {
    private val attrs = hashMapOf<String, Any>()
    fun setAttrs( key: String, value: Any){
        attrs[key] = value
    }
    val name: String
        get() = attrs["name"]}Copy the code

Some Persons have children and some do not, so different Person instances have different sets of properties. A Map is a good place to store attributes in this scenario.

The above code can be simplified with delegates:

class Person {
    private val attrs = hashMapOf<String, Any>()
    fun setAttrs( key: String, value: Any){
        attrs[key] = value
    }
    val name: String by attrs
}
Copy the code

Delegate the acquisition of name to a Map object. The magic is that you can get the name attribute value correctly from the map without even specifying the key. This is because the Kotlin library already defines the getValue() and setValue() extension functions for maps. The property name is automatically applied to the map key.

conclusion

  1. Kotlin delegates are divided intoCommissioned by classandAttribute to entrust. They all use keywordsbyTo delegate.
  2. A class delegate can delegate the implementation of a class to another object with concise syntax to reduce template code.
  3. A property delegate can delegate access to a property to another object to reduce template code and hide access details.
  4. Attribute to entrustThere are three ways of implementation, respectively is the extension method, implementationReadWritePropertyInterface, overloaded operator.

Recommended reading

  • Kotlin base | entrusted and its application
  • Kotlin basic grammar | refused to noise
  • Kotlin advanced | not variant, covariant and inverter
  • Kotlin combat | after a year, with Kotlin refactoring a custom controls
  • Kotlin combat | kill shape with syntactic sugar XML file
  • Kotlin base | literal-minded Kotlin set operations
  • Kotlin source | magic weapon to reduce the complexity of code
  • Why Kotlin coroutines | CoroutineContext designed indexed set? (a)
  • Kotlin advanced | the use of asynchronous data stream Flow scenarios