Delegation in Kotlin
In delegation mode, two objects participate in processing the same request, and the object receiving the request delegates the request to another object for processing.
Class delegate
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val b = Bean("hello world") Delegation(b).delegate() } } interface IDelegateListener { fun delegate() } class Bean(val s: String) : IDelegateListener { override fun delegate() { println("bean ----> $s") } } class Delegation(delegateListener: IDelegateListener) : IDelegateListener by delegateListener
From the output, we can see that the Delegation class does not implement the delegate() method in IDelegateListener, but delegates the method that should be implemented to Bean through the keyword by, which is implemented by Bean.
Delegate attribute
The grammar for defining a delegate attribute is val/var <property name>: <Type> by <expression>, followed by delegation of the attribute. The delegate attribute does not need to implement the interface, but only needs the setValue() getValue function modified by the operator. If it is a val attribute, setValue() is not required.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val bean = Bean() println("bean ---> ${bean.string}") bean.string="2" println("bean ---> ${bean.string}") } } class Bean { var string: String by Delegator() } class Delegator { var temp = "1" operator fun getValue(ref: Any?, p: KProperty<*>): String { return "Delegator --> ${p.name} --> $temp" } operator fun setValue(ref: Any?, p: KProperty<*>, value: String) { temp = value } }
Description of parameters:
- ref attribute owner
- Description of p attribute
- Value attribute value
Standard delegation
Delay attribute lazy
lazy() is a function that accepts a lambda and returns a Lazy < T > instance. The returned instance can be used as a delegation to implement the delayed attribute: the first call to get() executes the lambda expression passed to lazy() and records the result, followed by a call to lazy(). Using get() simply returns the result of the record.
class MainActivity : AppCompatActivity() { val lazyValue:String by lazy { println("1 ---->assignment") "lazy" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) println("2 ---->$lazyValue") println("3 ---->$lazyValue") } }
You can see from the results of the operation that "lazy" is assigned only when it is first used.
By default, the evaluation of lazy attributes is synchronized: the value is calculated only in one thread, and all threads see the same value. If the synchronization lock of the initialization delegate is not necessary so that multiple threads can execute simultaneously, then Lazy ThreadSafetyMode. PUBLICATION is passed as a parameter to the lazy() function. If you are sure that initialization will always occur on a single thread, you can use LazyThreadSafetyMode.NONE mode, which has no thread security guarantees and associated overhead.
Observable attribute
Delegates.observable() accepts two parameters: the initial value and the handler for modification. Whenever we assign a property, the handler is called (executed after assignment). It has three parameters: the assigned attribute, the old value and the new value.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val observableUse = ObservableUse() observableUse.s = "1" observableUse.i = "2" println("observable ----> ${observableUse.s}") println("observable ----> ${observableUse.i}") } } class ObservableUse { var s: String by Delegates.observable("0") { property, oldValue, newValue -> println("observable ----> $property,$oldValue,$newValue") } var i: String by Delegates.vetoable("3") { property, oldValue, newValue -> newValue.length > oldValue.length } }
By observable attributes, you can implement some observer pattern methods. If you want to intercept an assignment and "reject" it, use vetoable() instead of observable(). The handler passed to vetoable is called before the property is assigned a new value to take effect.
Practical application scenarios
By delegating, we can encapsulate some tool classes, such as Intent value Shared Prefrences value, etc.
/** * kotlin Delegated Value Transfer Tool */ class ExtraDelegator<out T>(private val extraKey:String , private val defaultValue:T) { private var extra: T? = null operator fun getValue(thisRef: AppCompatActivity, property: KProperty<*>): T { extra = getExtra(extra, extraKey, thisRef) return extra ?: defaultValue } operator fun getValue(thisRef: Fragment, property: KProperty<*>): T { extra = getExtra(extra, extraKey, thisRef) return extra ?: defaultValue } fun <T> extraDelegate(extra: String, default: T) = ExtrasDelegate(extra, default) fun extraDelegate(extra: String) = extraDelegate(extra, null) @Suppress("UNCHECKED_CAST") private fun <T> getExtra(oldExtra: T?, extraKey: String, thisRef: AppCompatActivity): T? = oldExtra ?: thisRef.intent?.extras?.get(extraKey) as T? @Suppress("UNCHECKED_CAST") private fun <T> getExtra(oldExtra: T?, extraKey: String, thisRef: Fragment): T? = oldExtra ?: thisRef.arguments?.get(extraKey) as T? }
Use:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Pass value content_tv.setOnClickListener { val intent = Intent(this@MainActivity, TestActivity::class.java) intent.putExtra("string","value") startActivity(intent) } } }
class TestActivity:AppCompatActivity(){ val string:String? by extraDelegate("string") // Acceptance by delegation override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) println("TestActivity ------> $string") } }
epilogue
Delegation and extension functions can encapsulate many interesting tool classes. The transition from Java to Kotlin feels like the transition from Eclipse to Android Studio was troublesome at first, but when used, it feels that: Ah, yes, it's OK. Now I don't want to write in Java anymore. For new things, I still have to dare to try.