观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

   kotlin的委托模式其实就是Java的代理模式的魔改(有更多的骚操作),是软件设计模式中的一项基本技巧。不管是代理还是委托都是可以从字面意思中理解,将一个"A对象"需要完成的工作交由另一位"B对象"完成。

 这里对代理模式理解不深刻的人,就会有疑问了,为什么在代码里不直接让A对象完成工作呢? 为什么要绕圈圈给B对象完成呢?

原因如下:

1.委托或者代理设计后,A对象可以保持代码上的简洁性,只需要关注提供什么与获取什么. 具体复杂的处理与数据组装工作流程可以隐藏在B对象里.

2.以一个不变的A对象,去请求多种代理. 就好比同样使用鸡蛋与面粉, B工厂可以生产蛋挞, 而C工厂可以生产面包. 而我们A店面统一将获取的产品称为面点类食品,并且保持A店面的干净简洁.


Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托并且有更灵活的各种形式的委托,功能更加强大。

在此篇模块里介绍了java的静态与动态代理: https://www.cnblogs.com/guanxinjing/p/14070749.html

参考 https://www.runoob.com/kotlin/kotlin-delegated.html

类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

//接口
interface IInfoService {
    fun setInfo()
}

//实现此接口的被委托的类
class InfoDao : IInfoService {
    override fun setInfo() {
        print("设置数据中")
    }
}

//通过关键字 by 建立委托类
class InfoDaoProxy(dao: InfoDao) : IInfoService by dao

fun main() {
    val dao = InfoDao()
    InfoDaoProxy(dao).setInfo()
}

属性委托

属性委托,顾名思义就是将一个类中的属性不是直接定义, 而是让一个代理类帮忙处理. 这样这个属性的获取值的会在代码上变的特别简洁一目了然,从而不需要在类某个方法或者运行代码中给这个属性设置值.

class User {
    val name: String by UserData()
}

class UserData {
    var name: String = "小明"
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("申请代理者 = $thisRef 属性名称 = ${property.name}" )
        return name
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("申请代理者 = $thisRef 属性名称 = ${property.name}" )
        this.name = value
    }
}

fun main() {
    val name = User().name
    println("用户名称1 $name")

    val userdata = UserData()
    val name2:String by userdata
    userdata.name = "小红"
    println("用户名称2 $name2")
}

结果:

申请代理者 = com.example.myapplication.proxy.User@3abbfa04 属性名称 = name
用户名称1 小明
申请代理者 = null 属性名称 = name2
用户名称2 小红

延迟委托 Lazy

在第一次调用的时候才执行代码块中的逻辑返回值,这里举例一个demo来演示它的特性。

代码:

    private val mStringTest by lazy {
        Log.e("zh", "正在执行mStringTest的委托代码块", )
        "这是委托内容=${mCount}"
    }
    var mCount = 0


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //略...
        mBinding.button.itemLayout.setOnIntervalClickListener {
            Log.e("zh", "第${mCount}次,点击了Button")
            Log.e("zh", "委托的值${mStringTest}", )
            mCount++
        }
    }

结果:

2022-06-02 14:00:19.948 17244-17244/com.lx.ev E/zh: 第0次,点击了Button
2022-06-02 14:00:19.949 17244-17244/com.lx.ev E/zh: 正在执行mStringTest的委托代码块
2022-06-02 14:00:19.949 17244-17244/com.lx.ev E/zh: 委托的值这是委托内容=0
2022-06-02 14:00:21.226 17244-17244/com.lx.ev E/zh: 第1次,点击了Button
2022-06-02 14:00:21.226 17244-17244/com.lx.ev E/zh: 委托的值这是委托内容=0
2022-06-02 14:00:21.754 17244-17244/com.lx.ev E/zh: 第2次,点击了Button
2022-06-02 14:00:21.754 17244-17244/com.lx.ev E/zh: 委托的值这是委托内容=0

这里点击了3次button按钮,从打印的日志里就可以清晰的了解延迟委托的特性了。首先它只在第一次调用全局变量的时候调用代码块返回委托数据。在后续多次调用都不会在触发延迟委托的代码块了。

延迟委托在Android开发中特别有用,需要好好掌握,因为Android中的很多context都是需要Activity或者Fragment实例完成后才能获得的,所以我们可以利用延迟委托在Activity的指定生命周期里委托需要的变量。下面举例2个使用场景:

委托ViewBinding

ViewBinding的委托,网上有很多奇奇怪怪的封装,比如泛型的Activity封装ViewBinding。 其实都不如直接用延迟委托灵活,清晰,简单。

    private val mBinding by lazy { SettingsActivitySettingsBinding.inflate(layoutInflater) }

委托ViewModel

    private val mViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }

延迟委托 Lazy 的线程安全模式

在创建延迟委托Lazy的时候,我们可以添加同步模式(LazyThreadSafetyMode),以确保在多线程下数据唯一与安全。此外,lazy是默认线程安全的,也就是如果你不填入线程安全模式,它默认LazyThreadSafetyMode.SYNCHRONIZED。这点你可以点击lazy查看源码了解到,什么都不填入的lazy使用SynchronizedLazyImpl。

下面有一个线程安全的懒汉同步块式单例例子来看看LazyThreadSafetyMode如何使用。(我这边因为举例填入了LazyThreadSafetyMode.SYNCHRONIZED实际上你可以不填,因为它是默认的)

class ChildRepository {

    companion object {
        val sInstance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ChildRepository() }
    }
}

它等价于Java的

public class SingleC {
    private static SingleC sIntance;
    private SingleC() {
    }
    public static SingleC getInstance() {
        if (sIntance == null) {
            synchronized (SingleC.class) {
                if (sIntance == null) {
                    sIntance = new SingleC();
                }
            }
        }
        return sIntance;
    }
}

LazyThreadSafetyMode的全部模式:

public enum class LazyThreadSafetyMode {

    /**
     * 锁用于确保只有一个线程可以初始化[Lazy]实例。
     * 线程安全
     */
    SYNCHRONIZED,

    /**
     * 初始化函数可以在并发访问未初始化的[Lazy]实例值时调用多次,但只有第一次返回的值将被用作[Lazy]实例的值。
     * 代码块可以反复执行,但是始终返回第一个实例
     */
    PUBLICATION,

    /**
     * 没有锁用于同步对[Lazy]实例值的访问;如果从多个线程访问实例,则其行为是未定义的。除非保证[Lazy]实例永远不会从多个线程初始化,否则不应该使用此模式。
     * 非线程安全
     */
    NONE,
}

可观察属性 Observable

在上面的类委托,属性委托与延迟委托都有一个特点。他们都是val的,就是委托之后无法改变值。如果我们需要改变委托的值,甚至观察委托值的改变,就需要使用Observable关键字

代码:

    private var mUserName: String by Delegates.observable("默认值") { property, old, new ->
        Log.e("zh", "委托代码块 property = ${property}  old = ${old}  new = ${new}")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //略...
        mBinding.button.itemLayout.setOnIntervalClickListener {
            Log.e("zh", "委托的值 = ${mUserName}")
            mUserName = "张三"
            Log.e("zh", "委托的值 = ${mUserName}")
            mUserName = "李四"
            Log.e("zh", "委托的值 = ${mUserName}")
        }
    }

结果:

2022-06-02 14:39:42.085 23870-23870/com.lx.ev E/zh: 委托的值 = 默认值
2022-06-02 14:39:42.572 23870-23870/com.lx.ev E/zh: 委托代码块 property = var com.lx.settings.ui.aboutdevice.fragment.DeviceInfoFragment.mUserName: kotlin.String  old = 默认值  new = 张三
2022-06-02 14:39:42.572 23870-23870/com.lx.ev E/zh: 委托的值 = 张三
2022-06-02 14:39:42.573 23870-23870/com.lx.ev E/zh: 委托代码块 property = var com.lx.settings.ui.aboutdevice.fragment.DeviceInfoFragment.mUserName: kotlin.String  old = 张三  new = 李四
2022-06-02 14:39:42.574 23870-23870/com.lx.ev E/zh: 委托的值 = 李四

可观察属性的委托在实际Android开发中有什么使用场景呢? 下面开始举例使用场景:

在SharedPreferences中使用:

object UserSP {
    private val mSp = SettingsApp.getApp().getSharedPreferences("UserSP", Context.MODE_PRIVATE)

    public var name by Delegates.observable(mSp.getString("name", "")) { kProperty: KProperty<*>, oldValue: String?, newValue: String? ->
        mSp.edit().putString("name", newValue).apply()
    }

    public var age by Delegates.observable(mSp.getInt("age", 0)) { kProperty: KProperty<*>, oldValue: Int?, newValue: Int? ->
        mSp.edit().putInt("age", newValue ?: 0).apply()
    }
}

获取与修改数据

UserSP.name = "张三"
Log.e("zh", "用户名称 = ${UserSP.name}")

上面的代码已经演示了在数据存储的情况使用观察委托的优势了,代码会变得特别的简洁,一目了然。你可以举例反三在数据库,网络请求(最好还是配合ViewModel与协程防止内存泄漏),View动画里大量使用。让代码更加简洁

委托与Map

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。但是好像在实际开发中好像没什么使用场景。

data class UserData(val map: Map<String, Any>) {
        val name: String by map
        val age: Int by map
        val sex:Boolean by map 
}

private fun initListener() {
  mBinding.back.setOnClickListener {
    val user = UserData(mapOf("name" to "张三", "age" to 11, "sex" to true))
    Log.e("zh", "用户数据 = ${user}")
  }
}

结果:

zh: 用户数据 = UserData(map={name=张三, age=11, sex=true})

如果使用 var 属性,需要把 Map 换成 MutableMap:

    data class UserData(val map: MutableMap<String, Any>) {
        var name: String by map
        var age: Int by map
        var sex: Boolean by map
    }

    private fun initListener() {
        mBinding.back.setOnClickListener {
            val user = UserData(mutableMapOf("name" to "张三", "age" to 11, "sex" to true))
            Log.e("zh", "用户数据 = ${user}")
            user.name = "李四"
            user.age = 12
            user.sex = false
            Log.e("zh", "用户数据 = ${user}")
        }
    }

结果:

/zh: 用户数据 = UserData(map={name=张三, age=11, sex=true})
/zh: 用户数据 = UserData(map={name=李四, age=12, sex=false})

Not Null

notNull 适用于那些无法在初始化阶段就确定属性值的场合。

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)

需要注意,如果属性在赋值前就被访问的话则会抛出异常。

 

End

posted on 2021-11-27 15:26  观心静  阅读(262)  评论(0编辑  收藏  举报