Kotlin 委托
委托和代理
- Delegate 委托和 Proxy 代理有点大同小异:代理比较严格,一般是同一个对象(实现了同一个接口或继承了同一个类),委托一般是不同的对象,但通常也对外提供被委托对象的同名方法,委托引用了被委托对象
- 应用:Android 中 AppCompatActivity 委托给一个叫 AppCompatDelegate 的抽象类,AppCompatDelegateImpl 是其实现,其中 AppCompatActivity#setContentView 方法就是委托了 AppCompatDelegate#setContentView 方法,可以在其前后增加额外的处理逻辑
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
private AppCompatDelegate mDelegate;
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
//
getDelegate().setContentView(layoutResID);
}
}
- PS:从我个人理解来说,Kotlin 委托更像是上面描述的代理,或者我这么理解:代理要求比委托高,但也不妨碍我们让委托变得严苛起来,让委托和代理一样严苛的话,那么此时的委托是不是就算是和代理是一样的了?所以换句话说委托范畴可以更大,是不是可以包含代理?或者代理是不是可以看成是一种更特殊的委托? —— 部分 kotlin 书籍里也是将委托直接当成代理的,另外一些 java 知识文章里,把被代理类(目标类)称为委托类
by 关键字
by 是 Kotlin 中用来实现委托的,编译器会生成委托模式的模板代码,使我们能够更方便的利用聚合来替代继承
- 类委托(需要实现同一个接口,不能是抽象类)
- 属性委托( getValue / setValue 方法的委托)
类委托
interface IPlayer{
fun start()
fun stop()
}
class DoPlayer: IPlayer{
override fun start() {
Log.e(TAG, "start: do" )
}
override fun stop() {
Log.e(TAG, "stop: do" )
}
}
//by 子句表示将 player 保存在 DelegatePlayer 的对象实例内部
class DelegatePlayer(private val player: IPlayer): IPlayer by player
val player = DelegatePlayer(DoPlayer())
player.start()
player.stop()
//PS:直接写 DoPlayer()
class DelegatePlayer2 : IPlayer by DoPlayer(){
}
属性委托
- 需要提供 getValue 或者 setValue 约定函数
private var test03: String by Delegate()
class Delegate {
private var testStr: String? = null
//operator重载运算符
operator fun getValue(thisRef: Any, property: KProperty<*>): String {
return testStr?:"tess"
}
operator fun setValue(thisRef: Any, property: KProperty<*>, s: String) {
testStr = s
}
}
PS:写着麻烦?写完 by Delegate() 后,AS 会提示报错需要生成;或者实现自带的接口即可
//val 属性实现 ReadOnlyProperty
class Delegate2 :ReadWriteProperty<Any,String>{
private var testStr: String? = null
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return testStr?:"tess"
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
testStr = value
}
}
常见应用
//简化项目里 ShareDataManage#save 和 ShareDataManage#get
private var spBooleanValue by SpBoolean(SP_NAME, "key1")
private var spStringValue by SpString(SP_NAME, "key2")
fun getBooleanValue(): Boolean = spBooleanValue
fun getStringValue(): String = spStringValue
fun setSpValue(value: Boolean) {
spBooleanValue = value
}
fun setSpValue(value: String) {
spStringValue = value
}
class SpString(private val spName: String, val key: String, private val defValue: String = "") {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
val sp = context.getSharedPreferences(spName, Context.MODE_PRIVATE)
return sp.getString(key, defValue)!!
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
val sp = context.getSharedPreferences(spName, Context.MODE_PRIVATE)
sp.edit().putString(key, value).apply()
}
}
Kotlin 标准库里内置的
- lazy 顶层函数
- Delegates.notNull
- Delegates.observable //其实 ObservableProperty 就是在 setValue 赋值的前后插入抽象方法,而 Delegates.observable 就采用理其中的 afterChange
- Delegates.vetoable //其实 ObservableProperty 就是在 setValue 赋值的前后插入抽象方法,而 Delegates.vetoable 就采用理其中的 beforeChange,但是比较特殊的就是 beforeChange 返回一个布尔值,决定了后续是否继续执行赋值,默认返回 true,如果返回 false 值则不继续赋值
by lazy
- 延迟委托
//lazy 是一个顶层函数,默认返回的是一个 Lazy 接口的实现类 SynchronizedLazyImpl,逻辑基本和 Java 的双重检验单例一致 ,线程安全,内部值只初始化一次
val gzLayout: GZLayout by lazy {
this.findViewById(R.id.gzLayout)
}
但是咋一看 SynchronizedLazyImpl 类没有找到 getValue 或者 setValue 约定函数, 如何实现属性委托呢?其实 Lazy.kt 文件里自带一个 Lazy 接口的 getValue 扩展函数,所以可以这么实现
/**
* An extension to delegate a read-only property of type [T] to an instance of [Lazy].
*
* This extension allows to use instances of Lazy for property delegation:
* `val property: String by lazy { initializer }`
*/
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
//扩展函数
fun <T : View> Activity.findView(id: Int) = lazy {
this.findViewById<T>(id)
}
//
val gzLayout: GZLayout by findView(R.id.gzLayout)
lateinit 关键字和 by Delegates.notNull
- 两者都需要自行保证在访问之前必须已经被初始化完成了
- lateinit 关键字只适用于引用类型
- by Delegates.notNull 适用于基本类型和引用类型
private lateinit var basicType01: Int //报错,不能用来修饰基本数据类型
private var basicType02: Int by Delegates.notNull() //可以这么写,不过访问之前必须自行保证已经被初始化