Kotlin Basics | Commissioning and Applications

Kotlin Basics | Commissioning and Applications

Delegation is a common pattern, it has nothing to do with programming language, that is, delegate what you originally did to another object to do it. Both the decorator pattern and the agent pattern 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 and inheritance have the same purpose, both for extending the class, but it uses a more complicated way: inheritance + composition. The decorator pattern extends its functions on the basis of reusing the original types and behaviors. For a detailed analysis of the decorator mode and the agent mode, you can click to use the combined design mode | remote agent mode for chasing girls .

The following is an example of the decorator pattern:

interface Accessory { fun name () : String//accessory name fun cost () : Int //accessory price fun type () : String//accessory category } Copy code

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

Feathers, rings, and earrings are three specific accessories, and their realization is as follows:

class Feather : Accessory{ override fun name () : String = "Feather" override fun cost () : Int = 20 override fun type () : String = "body accessory" } class Ring : Accessory{ override fun name () : String = "Ring" override fun cost () : Int = 30 override fun type () : String = "body accessory" } class Earrings : Accessory{ override fun name () : String = "Earrings" override fun cost () : Int = 15 override fun type () : String = "body accessory" } Copy code

Now it is necessary to add feather rings and feather earrings. According to the inherited idea, this can be achieved as follows:

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

The disadvantage of writing this way is that only the type is reused, and there is no reuse behavior. Every time a new type is added, a subcategory must be added, which will cause subcategories to expand. If you switch to the decorator pattern, you can reduce one subcategory:

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

Now feather rings and earrings can be expressed like this

Feather(Ring())
,
Feather(Earrings())
.

Feather
The application combination holds an abstract accessory, so that the behavior that is injected into the accessory can be reused.
name()
with
cost()
Added new functions on the basis of reuse behavior, and
type()
Directly delegated the implementation to
accessory
.

Using Kotlin's delegation syntax can be further simplified

Feather
class:

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

by
The keyword appears after the class name, indicating class delegation , that is, the realization of the class is delegated to an object, which must implement the same interface as the class, here it is
Accessory
interface. use
by
The advantage is to eliminate the template code, as shown above,
type()
The implementation of the interface can be omitted.

Lazy initialization once

Lazy initialization is also a common pattern: the initialization of an object is delayed until it is accessed for the first time. When initialization consumes a lot of resources, lazy initialization is particularly valuable.

Supporting attributes is an idiomatic technique for lazy initialization:

class BitmapManager { //Support attributes for storing a set of Bitmap 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 code

Support attributes

_bitmaps
Is private, it is used to store a set of Bitmap, and another of the same type
bitmaps
Used to provide access to a set of Bitmaps.

So only when the first visit

BitmapManager.bitmaps
When the time, Bitmap will be loaded. During the second visit, Bitmap will not be reloaded, and you can return directly
_bitmap
.

The above code is a Kotlin predefined function

lazy()
Internally used technology, with it, template code can be eliminated:

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

Keywords here

by
Appears after the attribute name, indicating attribute delegation , that is, entrusting the reading and writing of the attribute to another object. The delegated object must meet certain conditions:

  1. When performing property delegation for read-only variables modified by val, the delegated object must implement
    getValue()
    Interface, which defines how to get the variable value.
  2. When performing attribute delegation for var modified read-write variables, the delegated object must implement
    getValue()
    with
    setValue()
    Interface, which defines how to read and write variable values.

3.ways to achieve attribute delegation

lazy()
The return value of the 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 () : Boolean } Copy code

Lazy
The class is not directly implemented
getValue()
method. It uses another, more flexible way:

public inline operator Fun <T> Lazy <T> . getValue (thisRef: the Any , Property:? KProperty <*>) : T = value copy the code

getValue()
Is declared as
Lazy
The extension function of the class . This is Kotlin's unique feature that adds new functions to the class outside of the class. It is especially useful when the original class cannot be modified.

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

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

In this way, a new proxy class is created, and keywords are passed in the class

operator
Overloaded
getValue()
with
setValue()
These two operators correspond to value and setting operations respectively.

The last way is as follows (assuming the type of proxy is 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 code

Realize

ReadWriteProperty
In the interface
getValue()
with
setValue()
method.

Then you can use the proxy class like this:

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

The implementation behind the attribute delegation 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 code

Newly created

Delegate
The class will be stored in a supporting attribute
delegate
In, the setting of the delegated property and the realization of the value method are delegated to the proxy class.

After delegation, when accessing the delegated properties, it is like calling a 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

Commissioned application

1. Easier access to parameters

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

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

The code to get the value passed to the Activity or Fragment is very template. You can use delegates to hide the following details:

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

2. Get the map value more easily

The properties of some classes are not fixed, but sometimes more, sometimes less, that is, dynamic, such as:

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

some

Person
There are children, some don t, so it s different
Person
The set of attributes possessed by the instance is different. Use this scenario
Map
It is appropriate to store attributes.

The above code can be simplified with delegation:

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

will

name
The fetching is delegated to a map object. The magic is that you don t even need to specify
key
You can get the value of the name attribute from the map correctly. This is because the Kotlin standard library has already defined Map
getValue()
with
setValue()
Extension function. The attribute name will automatically be applied to the map key.

summary

  1. Kotlin delegation is divided into class delegation and attribute delegation . Keywords
    by
    To commission.
  2. Class delegation can use a concise syntax to delegate the implementation of a class to another object to reduce template code.
  3. Property delegation can delegate access to properties to another object to reduce template code and hide access details.
  4. There are three ways to implement property delegation , namely extension method and implementation
    ReadWriteProperty
    Interface, overloaded operator.

Recommended reading