一、类委托
Kotlin
interface DB{
fun insert()
}
class SqliteDB : DB {
override fun insert() {
println(" SqliteDB insert")
}
}
class MySql : DB{
override fun insert() {
println(" MySql insert")
}
}
class OracleDB : DB{
override fun insert() {
println(" OracleDB insert")
}
}
class CreateDB(db:DB) : DB by db
fun main() {
val db = CreateDB(MySql())
db.insert()
}
运行main()方法:
CreateDB类需要实现的接口(DB)方法委托给了具体实现类(MySql、OracleDB、SqliteDB)来实现,我认为这和设计模式中的策略模式如出一辙。
通过AndroidStudio的自带工具
Tools -> kotlin -> show kotlin Bytecode ,然后点Decompile,将字节码进行反编译得到java代码
也能佐证我们的想法:
二、委托属性
Kotlin
private var floatValue = 789.12
private var num by ::floatValue
fun main() {
println("num:$num")
num = 567.88
println("floatValue:$floatValue")
}
运行结果:
这个示例中将属性num 委托给了属性floatValue ,即对num 进行get和set操作其实都作用在了floatValue上,因此会有上面的运行结果。
同样我们可以看下反编译后的代码:
让我们稍感惊讶的是,经过转换后甚至都没有定义num这个属性,只是增加了getNum和setNum两个方法来获取和改变floatValue的值。
三、自定义委托
方式1:
Kotlin
class Test3{
var text:String by StringDelegate()
}
class StringDelegate{
operator fun getValue(owner:Any,property:KProperty<*>):String{
return "delegate value"
}
operator fun setValue(owner:Any,property:KProperty<*>,value:String){
println("value:$value")
}
}
fun main() {
val t = Test3()
println("text:${t.text}")
t.text = "haha"
}
要点:
- 想要成为可接受委托的类需要实现运算符重载方法,若委托类实现了getValue和setValue(即可读可写),则被委托属性text可用var修饰;若委托类只实现了getValue(即只读),则被委托属性text可用val修饰。
- getValue的返回值,和setValue的value参数值类型要与被委托属性类型一致。
- getValue和setValue中的owner参数必须是被委托属性text所属的类或其父类。
方式2:
Kotlin
class Test3{
var text:String by StringDelegate()
var text2:String by StringDelegate2()
}
class StringDelegate{
operator fun getValue(owner:Any,property:KProperty<*>):String{
return "delegate value"
}
operator fun setValue(owner:Any,property:KProperty<*>,value:String){
println("value:$value")
}
}
class StringDelegate2:ReadWriteProperty<Any,String>{
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "delegate value2"
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
println("value2:$value")
}
}
fun main() {
val t = Test3()
println("text:${t.text}")
t.text = "haha"
println("text2:${t.text2}")
t.text2 = "haha"
}
要点:
- 这种方式的原理就是方式一,只不过是系统Api给我们提供了一种相对方便的使用方式。
- 此方式需要出入两个泛型,第一个必须是被委托属性text所属的类或其父类,第二个是被委托属性的类型
- 若委托类支持可读可写则继承ReadWriteProperty,若只支持可读则继承ReadOnlyProperty
此方式原理:
方式3:
在上面的基础之上还有另一种方式,可以根据属性的一些特征来返回一个合适的委托实现类,有点类似于抽象工厂模式,根据特定的需求返回合适的实现,如下:
Kotlin
class Test3{
var text:String by StringDelegate()
var text2:String by StringDelegate2()
var text3:String by StringDelegateProvide()
}
class StringDelegate{
operator fun getValue(owner:Any,property:KProperty<*>):String{
return "delegate value"
}
operator fun setValue(owner:Any,property:KProperty<*>,value:String){
println("value:$value")
}
}
class StringDelegate2:ReadWriteProperty<Any,String>{
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "delegate value2"
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
println("value2:$value")
}
}
class StringDelegate3:ReadWriteProperty<Any,String>{
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "delegate value3"
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
println("value3:$value")
}
}
class StringDelegateProvide{
operator fun provideDelegate(thisRef: Any,property: KProperty<*>):ReadWriteProperty<Any,String>{
return if (property.name.contains("text3")){
StringDelegate3()
}else{
StringDelegate2()
}
}
}
fun main() {
val t = Test3()
println("text:${t.text}")
t.text = "haha"
println("text2:${t.text2}")
t.text2 = "haha"
println("text3:${t.text3}")
}
四、使用案例
通过委托封装Mmkv存取值的过程
Kotlin
import com.blankj.utilcode.util.GsonUtils
import com.tencent.mmkv.MMKV
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class MMKVDelegate<T>(
private val mmkv: MMKV,
private val cls: Class<T>,
private val key: String,
private val defaultValue: T
) : ReadWriteProperty<Any?, T> {
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when {
cls.isEnum -> {
val enumValues = cls.enumConstants
val enumValue = mmkv.decodeString(key, null)
enumValues?.firstOrNull { it.toString() == enumValue } ?: defaultValue
}
cls.isData || MMKVDelegatedClass::class.java.isAssignableFrom(cls) -> runCatching {
GsonUtils.fromJson(
mmkv.decodeString(
key
), cls
)
}.getOrNull() ?: defaultValue
cls.isAssignableFrom(String::class.java) -> mmkv.decodeString(
key, defaultValue as String
) as T
cls.isAssignableFrom(Int::class.java) -> mmkv.decodeInt(
key, defaultValue as Int
) as T
cls.isAssignableFrom(Boolean::class.java) -> mmkv.decodeBool(
key, defaultValue as Boolean
) as T
cls.isAssignableFrom(Float::class.java) -> mmkv.decodeFloat(
key, defaultValue as Float
) as T
cls.isAssignableFrom(Long::class.java) -> mmkv.decodeLong(
key, defaultValue as Long
) as T
cls.isAssignableFrom(Double::class.java) -> mmkv.decodeDouble(
key, defaultValue as Double
) as T
cls.isAssignableFrom(ByteArray::class.java) -> mmkv.decodeBytes(
key, defaultValue as ByteArray
) as T
else -> throw IllegalArgumentException("Unsupported type.")
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val ok = when {
cls.isEnum -> mmkv.encode(key, value?.toString())
cls.isData || MMKVDelegatedClass::class.java.isAssignableFrom(cls) -> mmkv.encode(
key,
GsonUtils.toJson(value)
)
cls.isAssignableFrom(String::class.java) -> mmkv.encode(key, value as String)
cls.isAssignableFrom(Int::class.java) -> mmkv.encode(key, value as Int)
cls.isAssignableFrom(Boolean::class.java) -> mmkv.encode(key, value as Boolean)
cls.isAssignableFrom(Float::class.java) -> mmkv.encode(key, value as Float)
cls.isAssignableFrom(Long::class.java) -> mmkv.encode(key, value as Long)
cls.isAssignableFrom(Double::class.java) -> mmkv.encode(key, value as Double)
cls.isAssignableFrom(ByteArray::class.java) -> mmkv.encode(key, value as ByteArray)
else -> throw IllegalArgumentException("Unsupported type.")
}
if (!ok) {
throw MMKVDelegateException.EncodeException()
}
}
}
Kotlin
import kotlin.jvm.internal.Reflection
val <T> Class<T>.isData: Boolean get() = Reflection.getOrCreateKotlinClass(this).isData
假设我们在项目中需要在本地记录一些App行为信息
Kotlin
object AppBehavior {
private const val TAG = "AppBehavior"
private const val MMKV_ID = "AppBehavior"
//记录App启动次数
var appStartTime: Int by SettingValue(
Int::class.java,
Key.APP_START_TIME,
0
)
/**
* APP行为的属性委托
* 值存储成功时,自动发送UserLocalSettingChangedEvent事件
* @param cls 属性的类型
* @param key 存储到MMKV的key
* @param defaultValue 从MMKV获取失败时,返回的默认值
*/
private class SettingValue<T>(
private val cls: Class<T>,
private val key: Key,
private val defaultValue: T
) : MMKVDelegate<T>(MMKV.mmkvWithID(MMKV_ID), cls, key.name, defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
runCatching {
super.setValue(thisRef, property, value)
}.onFailure {
Log.e(TAG, it.toString())
if (it !is MMKVDelegateException) {
throw it
}
}
}
}
}
调用:
Kotlin
AppBehavior.appStartTime++