往期文章
- 前言
委托(Delegation),可能是 Kotlin 里最容易被低估的特性。
提到 Kotlin,大家最先想起的可能是扩展
,其次是协程
,再要不就是空安全
,委托根本排不上号。但是,在一些特定场景中,委托的作用是无比犀利的。
本文将系统介绍 Kotlin 的委托,然后在实战环节中,我会尝试用委托 + 扩展函数 + 泛型
,来封装一个功能相对完整的 SharedPreferences 框架。
- 前期准备
- 将 Android Studio 版本升级到最新
- 将我们的 Demo 工程 clone 到本地,用 Android Studio 打开:
github.com/chaxiu/Kotl… - 切换到分支:
chapter_07_delegate
- 强烈建议各位小伙伴小伙伴跟着本文一起实战,实战才是本文的精髓
- 委托类(Class Delegation)
委托类,通过关键字 by
可以很方便的实现语法级别的委托模式
。看个简单例子:
1 | kotlin复制代码interface DB { |
这种委托模式在我们实际编程中十分常见,UniversalDB 相当于一个壳,它提供数据库存储功能,但并不关心它怎么实现。具体是用 Sql 还是 GreenDao,传不同的委托对象进去就行。
以上委托类的写法,等价于以下 Java 代码:
1 | java复制代码class UniversalDB implements DB { |
各位可不要小看这个小小的by
,上面的例子中,接口只有一个方法,所以 Java 看起来也不怎么麻烦,但是,当我们想委托的接口方法很多的时候,这个by
能极大的减少我们的代码量。
我们看一个复杂点的例子,假设我们想对MutableList
进行封装,并且增加一个方法,借助委托类的 by
,几行代码就搞定了:
1 | kotlin复制代码// 这个参数才是干活的,所有接口实现都被委托给它了,实现这一切只需要 ↓ |
如果是在 Java 里,那就不好意思了,呵呵,我们必须 implements 这么多方法:
想想要写那么多的重复代码就心累,是不是?
注:Effective Java 里面提到过:组合优于继承
(Favor composition over inheritance),所以在 Java 中,我们也会尽可能多使用接口(interface)。借助 Kotlin 提供的委托类,我们使用组合类会更方便。结合上面的例子,如果需要实现的接口有很多个,委托类真的可以帮我们省下许多的代码量。
- 委托属性(Property Delegation)
委托属性
,它和委托类虽然都是通过 by 来使用的,但是它们完全不是一回事。委托类委托出去的是它的接口实现;委托属性,委托出去的是属性的 getter
,setter
。我们前面经常提到的 val text = by lazy{}
,其实就是将 text 的 getter
委托给了 lazy{}
。
1 | arduino复制代码val text: String = by lazy{} |
- 自定义委托属性
Kotlin 的委托属性用起来很神奇,那我们怎么根据需求实现自己的属性委托呢?看看下面的例子:
1 | kotlin复制代码class Owner { |
我想为上面的 text
属性提供委托,应该怎么做?请看下面例子的注释:
1 | kotlin复制代码class StringDelegate(private var s: String = "Hello") { |
小结:
var
—— 我们需要提供getValue
和setValue
val
—— 则只需要getValue
operator
—— 是必须的,这是编译器识别委托属性的关键。注释中已用 ⚡ 标注了。property
—— 它的类型一般固定写成KProperty<*>
value
—— 的类型必须是委托属性的类型,或者是它的父类。也就是说例子中的value: String
也可以换成value: Any
。注释中已用↓
标注了。thisRef
—— 它的类型,必须是属性所有者的类型,或者是它的父类。也就是说例子中的thisRef: Owner
也可以换成thisRef: Any
。注释中已用 👇 标注了。
以上是委托属性
中比较重要的细节,把握好这些细节,我们写自定义委托就没什么问题了。
- 实战
又到了我们熟悉的实战环节,让我们来做点有意思的事情吧。
- 热身
前面的章节我们实现过一个简单的 HTML DSL,一起看看如何使用委托来优化之前的代码吧!如果仔细看的话,各位应该能发现这一处代码看着非常不爽,明显的模版代码:
1 | kotlin复制代码class IMG : BaseElement("img") { |
要是能用委托属性来写就好了:
1 | kotlin复制代码// 这代码看着真舒服 |
按照前面讲的自定义委托属性的要求,我们很容易就能写出这样的代码:
1 | kotlin复制代码// 对应 IMG 类 |
按照前面讲的,thisRef 的类型可以是父类,所以写成这样问题也不大:
1 | kotlin复制代码// 变化在这里 |
改成 thisRef: Any
的好处是,以后在任意类里面的 String 属性,我们都可以用这种方式去委托了,比如:
1 | kotlin复制代码class Test { |
思考题1
请问:上面的 thisRef: Any
改成 thisRef: Any?
是否会更好?为什么?
思考题2
官方其实有 map 委托的实现,官方的写法好在哪里?(答案藏在 GitHub Demo 代码注释里。)
- 委托属性 + SharedPreferences
在上一章 扩展函数
,我们使用高阶函数
+扩展函数
,简化了 SharedPreferences,但那个用法仍然不够简洁,那时候我们是这么用的,说实话,还不如我们 Java 封装的 PreferenceUtils 呢。
1 | kotlin复制代码// MainActivity.kt |
假如我们能这么做呢:
1 | kotlin复制代码private var spResponse: String by PreferenceString(SP_KEY_RESPONSE, "") |
这就很妙了!
这样一个委托属性其实也很容易实现对不对?
1 | kotlin复制代码operator fun getValue(thisRef: Any?, property: KProperty<*>): String { |
为了让它支持默认值
,commit()
,我们加两个参数:
1 | kotlin复制代码class PreferenceString( |
这也很简单,对不对?
以上代码仅支持 String
类型,为了让我们的框架支持不同类型的参数,我们可以引入泛型
:
1 | kotlin复制代码class PreDelegate<T>( |
以上所有代码都在 GitHub,欢迎 star
fork
:github.com/chaxiu/Kotl…。
Delegation 测试代码的细节看这个 GitHub Commit
Delegation HTML 的细节看这个 GitHub Commit
Delegation SharedPreferences 的细节看这个 GitHub Commit
思考题3:
以上代码仅支持了几个基础类型,能否扩展支持更多的类型?
思考题4:
以这样的封装方式,下次我们想为 PreDelegate 增加其他框架支持,比如腾讯的 MMKV
,应该怎么做?
思考题5:
有没有更优雅
的方式封装 SharedPreferences?
- 结尾:
- 委托,分为
委托类
,委托属性
- 委托类,可以方便快捷的实现
委托模式
,也可以配合接口来实现类组合
- 委托属性,既可以提高代码的
复用率
,还能提高代码的可读性
。 - 委托类,它的原理其实就是编译器将
委托者
,被委托者
两者对应的接口方法绑定
。 - 委托属性,它的原理是因为编译器会识别特定的
getter
,setter
,如果它们符合特定的签名要求,就会被解析成Delegation
都看到这了,给点个赞呗!
回目录–>【Kotlin Jetpack 实战】
本文转载自: 掘金