The articles
Kotlin Jetpack: The Beginning
00. The Kotlin Pit Guide for Java Developers
03. Kotlin programming’s Triple Realm
04. Kotlin higher-order Functions
05. Kotlin Generics
06. Kotlin Extension
1. Introduction
Delegation is probably the most underrated feature of Kotlin.
When it comes to Kotlin, the first thing that comes to mind is extension, followed by coroutines, and then empty security. However, in certain scenarios, delegation can be extremely useful.
This article will systematically introduce Kotlin’s delegation, and then in the actual practice, I will try to use delegation + extension functions + generics to encapsulate a relatively complete SharedPreferences framework.
2. Prepare
- Update the Android Studio version to the latest
- Clone our Demo project locally and open it with Android Studio:
Github.com/chaxiu/Kotl…
- Switch to branch:
chapter_07_delegate
- I strongly suggest that you follow this article to practice, the actual combat is the essence of this article
3. Class Delegation
Delegate classes, with the keyword BY, make it easy to implement syntax-level delegate patterns. Here’s a simple example:
interface DB {
fun save(a)
}
class SqlDB() : DB {
override fun save(a) { println("save to sql")}}class GreenDaoDB() : DB {
override fun save(a) { println("save to GreenDao")}}// The argument delegates the interface implementation to DB via BY
/ / left left
class UniversalDB(db: DB) : DB by db
fun main(a) {
UniversalDB(SqlDB()).save()
UniversalDB(GreenDaoDB()).save()
}
SQL save to GreenDao */
Copy the code
This delegation pattern is so common in our actual programming that UniversalDB is a shell that provides database storage functionality but doesn’t care how it is implemented. Whether you want to use Sql or GreenDao, just pass different delegate objects into it.
The above delegate class is equivalent to the following Java code:
class UniversalDB implements DB {
DB db;
public UniversalDB(DB db) { this.db = db; }
// Manually override the interface to delegate save to db.save()
@Override/ / left
public void save(a) { db.save(); }}Copy the code
In the example above, the interface has only one method, so Java doesn’t seem too cumbersome. However, when we want to delegate many interface methods, this BY can greatly reduce the amount of code we need.
Let’s look at a slightly more complicated example. Suppose we want to encapsulate a MutableList and add a method, using the delegate class by, in a few lines:
// This parameter does the work. All interface implementations are delegated to it
/ / left
class LogList(val log: () -> Unit.val list: MutableList<String>) : MutableList<String> by list{
fun getAndLog(index: Int): String {
log()
return get(index)
}
}
Copy the code
In Java, we have to implements so many methods:
It’s tiring to think about writing all that code again and again, isn’t it?
Note: Effective Java mentions:Composition over Inheritance
Favor Composition over Inheritance, so in Java, we use interfaces as much as possible. With the delegate classes provided by Kotlin, it’s easier to use composite classes. Combined with the above example, delegate classes can really save a lot of code if there are multiple interfaces to implement.
4. Property Delegation
Delegate properties, which and delegate classes are both used by by, but they are completely different. A delegate class delegates its interface implementation; Delegate properties, delegate out getters, setters for properties. Val text = by lazy{} delegate the getter for text to lazy{}.
Val text: String get() {lazy{}} val text: String get() {lazy{}}Copy the code
5. Customize delegate properties
Kotlin’s delegate property works wonders, so how do we implement our own delegate property on demand? Consider the following example:
class Owner {
varText: String = "Hello"}Copy the code
I want to delegate to the text property above. What should I do? Take a look at the comments for the example below:
class StringDelegate(private var s: String = "Hello") {
// The class corresponding to text corresponds to the type of text
/ / β‘ π left
operator fun getValue(thisRef: Owner, property: KProperty< * >): String {
return s
}
// The class corresponding to text corresponds to the type of text
/ / β‘ π left
operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
s = value
}
}
/ / π
class Owner {leftvar text: String by StringDelegate()
}
Copy the code
Summary:
var
B: We need to providegetValue
andsetValue
val
— onlygetValue
operator
This is the key for the compiler to recognize delegate attributes. The comments are marked with β‘.property
Its type is usually written in a fixed wayKProperty<*>
value
The type of — must beEntrusted propertyType, or its superclass. That is, in the examplevalue: String
You can also change it tovalue: Any
. Used in commentsleft
The annotation.thisRef
— It must be of typeProperty ownerType, or its superclass. That is, in the examplethisRef: Owner
You can also change it tothisRef: Any
. The comments are marked with π.
The above isEntrusted property
The more important details, master these details, we write custom delegate no problem.
6. The actual combat
It’s time for our familiar practice, so let’s do something interesting.
Warm up 7.
In the previous section, we implemented a simple HTML DSL. Let’s see how you can optimize the code using delegates. If you look closely, you can see that this code looks very uncomfortable. The template code is obvious:
class IMG : BaseElement("img") {
var src: String
get() = hashMap["src"]!!!!!set(value) {
hashMap["src"] = value
}
/ / write
// Look at the duplicate template code
/ / left
var alt: String
get() = hashMap["alt"]!!!!!set(value) {
hashMap["alt"] = value
}
}
Copy the code
It would be nice to write it with a delegate property:
// This code looks very comfortable
class IMG : BaseElement("img") {
var src: String by hashMap
var alt: String by hashMap
}
Copy the code
Following the custom delegate attributes described earlier, we could easily write code like this:
// corresponds to the IMG class
/ / π
operator fun HashMap
,>.getValue(thisRef: IMG, property: KProperty< * >): String? =
get(property.name)
operator fun HashMap<String, String>.setValue(thisRef: IMG, property: KProperty<*>, value: String) =
put(property.name, value)
Copy the code
ThisRef can be a parent class, so it is not a problem to write thisRef like this:
// The change is here
/ / π
operator fun HashMap
,>.getValue(thisRef: Any, property: KProperty< * >): String? =
get(property.name)
operator fun HashMap<String, String>.setValue(thisRef: Any, property: KProperty<*>, value: String) =
put(property.name, value)
Copy the code
ThisRef: Any, we can delegate Any String attribute in this way, for example:
class Test {
var src: String by hashMap
var alt: String by hashMap
}
Copy the code
There is 1
Excuse me: abovethisRef: Any
tothisRef: Any?
Would it be better? Why is that?
There is 2
There is an official implementation of the map delegate. What’s so good about the official way of writing it? (The answer lies inGitHub DemoIn the code comments.
Delegate attribute + SharedPreferences
In the previous chapter on extension functions, we simplified SharedPreferences by using higher-order functions plus extension functions, but that usage is still not as neat as the way we used it at the time, and frankly, not as good as our Java package PreferenceUtils.
// MainActivity.kt
private val preference: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) {
getSharedPreferences(SP_NAME, MODE_PRIVATE)
}
// Read cache
private val spResponse: String? by lazy(LazyThreadSafetyMode.NONE) {
preference.getString(SP_KEY_RESPONSE, "")}private fun display(response: String?). {
// Update the cache
preference.edit { putString(SP_KEY_RESPONSE, response) }
}
Copy the code
What if we could do this:
private var spResponse: String by PreferenceString(SP_KEY_RESPONSE, "")
// Display cache
display(spResponse)
// Update the cache
spResponse = response
Copy the code
This is wonderful!
A delegate property like this is actually pretty easy to implement, right?
operator fun getValue(thisRef: Any? , property: KProperty<*>): String { return prefs.getString(name, "") ? : default } operator fun setValue(thisRef: Any? , property: KProperty<*>, value: String) { prefs.edit().apply() }Copy the code
To make it support the default, commit(), we add two arguments:
class PreferenceString(
private val name: String,
private val default:String ="".private val isCommit: Boolean = false.private val prefs: SharedPreferences = App.prefs) {
operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
returnprefs.getString(name, default) ? : default }operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String) {
with(prefs.edit()){
putString(name, value)
if (isCommit) {
commit()
} else {
apply()
}
}
}
}
Copy the code
That’s easy, right?
The above code only supports String. To make our framework support different types of parameters, we can introduce generics:
class PreDelegate<T>(
private val name: String,
private val default: T,
private val isCommit: Boolean = false.private val prefs: SharedPreferences = App.prefs) {
operator fun getValue(thisRef: Any? , property:KProperty< * >): T {
returngetPref(name, default) ? : default }operator fun setValue(thisRef: Any? , property:KProperty<*>, value: T){ value? .let { putPref(name, value) } }private fun <T> getPref(name: String, default: T): T? = with(prefs) {
val result: Any? = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type is not supported")
}
result as? T
}
private fun <T> putPref(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type is not supported")}if (isCommit) {
commit()
} else {
apply()
}
}
}
Copy the code
All of the above code is on GitHub. Welcome to Star Fork: github.com/chaxiu/Kotl… .
For details of the Delegation test code see thisGitHub Commit
Delegation HTML for details see thisGitHub Commit
Delegation SharedPreferences for details see thisGitHub Commit
Question 3:
The code above supports only a few basic types. Can you extend it to support more types?
There is 4:
With this encapsulation, next time we want to add other framework support for PreDelegate, such as Tencent’sMMKV
What should be done?
There is 5:
Is there anyMore elegant
How to encapsulate SharedPreferences?
9. The ending:
- Commission, divided into
Delegate class
.Entrusted property
- Delegate classes can be implemented quickly and easily
Delegate pattern
, can also be implemented with the interfaceKind of combination
- Delegate attributes, which can improve code
The reuse rate of
Can also improve the codereadability
. - Delegate class, which is basically what the compiler will do
consignor
.delegatee
The corresponding interface methodsThe binding
. - Delegate property, which works because the compiler recognizes specific
getter
.setter
If they meet specific signature requirements, they are resolved toDelegation
All see this, give it a thumbs up!
γKotlin Jetpack γ