Kotlin委托的思想与应用

399 阅读6分钟

0.前言

kotlin的委托文章使用教程不胜枚举,不敢说比各位前辈们写的更好。看文章时突然来了一点点灵感,就像聊聊委托代表的思想,该怎么利用委托到实际开发中。

kotlin官网: 委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它

1.继承的问题

官方说委托模式是实现继承的一个很好的替代方式。那么继承有什么问题,为什么要提代它。

对于Android程序员来说,最常用到继承的地方是BaseActivity,BaseFragment。那么项目中为什么会要设计这两个Base类呢?

对于Activity 和 fragment 可能存在很多通用功能,不可能每个子类把这些通用功能都实现一遍,所以把代码提取到Base类实现,子类继承Base类,就拥有了Base类中包含的所有功能。

现在假定BaseActivity中存在如下功能:

  1. 数据埋点
  2. 沉浸式导航栏
  3. 公共弹窗
  4. 业务逻辑
  5. 模板代码

随着业务的发展BaseActivity的代码可能会越来越多,越来越乱,不同功能不同职责的代码都充斥再一个类中非常不好维护。

由于Java和kotlin只支持单继承,假定经过分析后抽象出三个父类,A继承B,B继承C,把业务根据职责差分到不同的类中,形成一条继承线路。

这就造成了继承层级过多的问题。并且继承把扩展功能固化到线路中每个子类节点上了,如果想创建子类并想让他包含某个已经存在的扩展功能,就必须继承父类,顺带拥有了父类继承线路上所有的扩展功能。虽然可能这个子类不需要某个功能,但是它不得不要。

当然也可以重开一条继承支线,只包含需要的功能,这样做的后果是使类关系过于复杂。如果不拆分就会导致Base类代码爆炸。

小结:

继承也不是一无是处,如果类之间的继承结构稳定,关系不复杂就可以使用继承

当代码复杂时过度使用继承可能会带来如下问题:

  1. 类关系复杂
  2. 子类可能会功能冗余
  3. 违背单一职责,代码爆炸,不好维护

2.Java中委托的实现方式

百度百科: 在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

上述问题可以利用委托模式简化,利用官方AppCompatActivity 举例。可以看出AppCompatActivity 自己没有任何实现,只是接受外部方法调用后,在转交给AppCompatDelegate 委托对象的同名方法。

AppCompatDelegate 是真正做事情的。

public class AppCompatActivity {

    private AppCompatDelegate mDelegate;
		//...省略代码
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(getDelegate().attachBaseContext2(newBase));
    }

    @Override
    public void setTheme(@StyleRes final int resId) {
        super.setTheme(resId);
        getDelegate().setTheme(resId);
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        getDelegate().onPostCreate(savedInstanceState);
    }

   //...省略代码
}

我们也可以借鉴这种写法,把BaseActivity中的复杂逻辑,委托给一个或者多个类实现,简单示例如下:

open class BaseActivity:AppCompatActivity() {
    private val dataDelegate =DataDelegate()
    private val uiDelegate =UIDelegate()
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        //1.数据埋点
        dataDelegate.onCreate()
        //2.沉浸式导航栏
        uiDelegate.onCreate()
    }
}

委托背后的思想:多用组合,少用继承,代码职责单一

实际上继承也可以看做是一种委托,只不过继承是将子类功能全部委托给父类。

组合是将职责不同的代码,分散到不同的类中实现,根据需要自由组合,更符合职责单一的思想

3.Kotlin委托

kotlin官网: 委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它

经过上面的讨论对于为什么要使用委托应该有一定了解。kotlin通过by关键字从语法层面天生支持委托。

先实现一个小案例,结合委托和Lifecycle实现日志输出的功能 代码如下:

  1. 定义ILogPrinter 接口,继承LifecycleEventObserver 拥有监听生命周期的能力
  2. 创建实现类LogPrinter
    1. 实现print()方法
    2. 实现onStateChanged() jie'sh
    3. 打印日志
  3. LogActivity
    1. 类头声明接口并通过by关键字把实现委托给LogPrinter ILogPrinter by LogPrinter()
    2. 在onCreate()调用lifecycle.addObserver(this) 绑定lifecycle组件即可
    3. 在当前类中任意位置可随意调用print()函数
interface ILogPrinter :LifecycleEventObserver{
    fun print(tag:String,message:String)
}

class LogPrinter : ILogPrinter {
    override fun print(tag: String, message: String) {
        Log.d(tag, message)
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        val tag = source.javaClass.name
        var message = ""
        message = when (event) {
            Lifecycle.Event.ON_CREATE -> {
                "ON_CREATE"
            }
            Lifecycle.Event.ON_RESUME -> {
                "ON_RESUME"
            }
            Lifecycle.Event.ON_DESTROY -> {
                "ON_DESTROY"
            }
            else -> {
                ""
            }
        }
        print(tag, message)
    }
}

class LogActivity : AppCompatActivity(), ILogPrinter by LogPrinter() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(this)
        print("LogActivity","onCreate函数中调用啦")
    }

		private fun test(){
        print("LogActivity","test函数中调用啦")
    }
}

小总结

虽然案例很简单,但是也能够体现出委托的特性:把部分实现交由委托类。在实际开发中功能复用 或者 单个类代码过于复杂时可以尝试使用委托优化

语法则非常简单:ILogPrinter by LogPrinter() 类型 by 实现类

属性委托

val/var <属性名>: <类型> by <表达式> by 后面的表达式 是一个对象,属性委托是把当前属性的getter,setter交给委托对象实现。如下代码 通过代理模拟属性默认的赋值取值过程,没有任何其他操作。

private var text :String by StringDelete()

    class StringDelete {
        private var value =""
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            Log.d("StringDelete","getValue 属性名:${property.name} ")
            return value
        }

        operator fun setValue(thisRef: Any?, property: KProperty<*>, s: String) {
            Log.d("StringDelete","setValue  属性名:${property.name}  属性值:${s}")
            value = s
        }
    }

模拟一个需求,为项目中某写字符串变量添加缓存操作,取值时从缓存中去,赋值时保存到缓存。改动如下:

private var text :String by StringDelete()

class StringDelete {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return Cache.get(property.name)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, s: String) {
        Cache.set(property.name,s)
    }
}

可见属性委托也是同样的思想,当有大量属性存在重复逻辑时就可使用属性委托进行代码优化。

Kotlin提供两个接口实现属性属性委托:ReadOnlyProperty ReadWriteProperty 分别对应只读属性和读写属性。方法是一样的只不过预定义了接口 使用更方便

class StringDelete1 :ReadWriteProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
    }
}

class StringDelete2 :ReadOnlyProperty<Any,String>{
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
    }
}

4.参考文章

更多的使用细节:

一文彻底搞懂Kotlin中的委托 - 掘金 (juejin.cn)

Kotlin | 委托机制 & 原理 & 应用 - 掘金 (juejin.cn)

应用参考:

Kotlin 类委托(一):如何把一个列表页优化到十几行代码 - 掘金 (juejin.cn)

Android | ViewBinding 与 Kotlin 委托双剑合璧 - 掘金 (juejin.cn)