Kotlin中的委托 - Delegation

1. 委托模式与mixin

委托模式即是委托类将某种处理/请求全权交给被委托类处理,自身只是包含被委托类的实例引用,或者实现被委托类的接口方法。调用者看来是委托类在完成请求,而实际工作的则是被委托类

这么做除了满足具体的业务需求外,也符合设计模式中用组合代替继承的原则,解耦了类之间is-a的继承关系。之前学习Dart时遇到的mixin关键字就是体现了这种原则。

  • Dart: mixin
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  mixin Delegate {
    void onTaskDone() {
      print("delegate finished works");
    }
  }

  class Worker with Delegate {
    //I can't do anything
  }

  void main(List<String> arguments) {
    final worker = new Worker();
    worker.onTaskDone(); 
    //Worker is-not-a Delegate but can delegates tasks to the Delegate, and main class has no idea about it.
  }

在上述例子中,Worker并没有继承Delegate类,却通过with关键字获得了使用Delegate类方法的权利。其他各种懒惰的类都可以通过同样的方法请Delegate类替自己干活,而不必成为Delegate的一员,降低了代码重用的门槛。

2. Kotlin中的by关键字

Java中实现委托模式需要引用实例·继承接口等一顿操作,kotlin则省去了样板代码,用by对委托实现原生支持。

根据官方文档,by关键字具有两种用法

  1. 委托接口的实现
  2. 委托属性的读写

2-1. 委托接口的实现

  • 语法
    1
    2
    3
    4
    5
    6
    
      class Worker(impl: DelegateInterface): DelegateInterface by impl{} 
      //Call
      fun main() {
        val impl = DelegateInterfaceImpl() 
        Worker(impl).methodA() //the method is implemented by impl but not the worker itself.
      }
    
  • 使用场景
    实现接口需要实现接口中所有的方法,哪怕并非全部必要。应用委托,我们可以先实现一个拥有所有接口方法的委托类,再在当前类中选择性地重写需要的方法
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
      interface DelegateInterface {
        fun methodA()
        fun methodB()
      } 
    
      class DelegateInterfaceImpl: DelegateInterface {
        override fun methodA() {}
        override fun methodB() {}
      }
    
      class WorkerA(impl: DelegateInterface): DelegateInterface by impl{
        override fun methodA() {} 
        //only need to implement what you need
      } 
    
      class WorkerB(impl: DelegateInterface): DelegateInterface by impl{
        override fun methodB() {} 
        //only need to implement what you need
      } 
    
      //Call
      fun main() {
        val impl = DelegateInterfaceImpl() 
        WorkerA(impl).methodA() //only overridden method will be called
        WorkerB(impl).methodB() //only overridden method will be called
      }
    

2-2. 委托属性的读写(以委托Lazy实例为例)

  • 语法

    1
    
    val/var <属性名>: <类型> by <表达式>
    

    属性委托本质上是把get( )/set( )方法委托给委托类的getValue()/setValue()方法。对于var(可变)属性,委托类必须提供setValue方法,不然会报错。

  • 标准委托
    kotlin标准库提供了生产Lazy,Observable等特殊对象的工厂方法。生产出的对象可以被委托。 下面的例子描述了委托给自定义的类,和调用标准委托by lazy的用法:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    import kotlin.reflect.KProperty
    
    class test() {
        //can be var or val; if var, should provide setValue method
        var deleProperty: String by Delegate()
        val lazyProperty: String? by lazy(LazyThreadSafetyMode.NONE){
            println("executed only once")
            "lazy initialized"
        }
    }
    
    class Delegate() {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return "$thisRef, thank you for delegating '${property.name}' to me!"
        }
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
            println("$value has been assigned to '${property.name}' in $thisRef.")
        }
    
    }
    
    fun main() {
        val test = test()
    
        println("1. " + test.deleProperty) //1. test@7ef20235, thank you for delegating 'deleProperty' to me!
        println("2. " + test.lazyProperty) //executed only once ¥n 2. lazy initialized
        println("3. " + test.lazyProperty) //3. lazy initialized
    
        test.deleProperty = "new value" // new value has been assigned to 'deleProperty' in test@7ef20235.
        println("4. " + test.deleProperty) //4. test@7ef20235, thank you for delegating 'deleProperty' to me!   
    }
    

    console output

    1. test@7ef20235, thank you for delegating 'deleProperty' to me!
    executed only once
    2. lazy initialized
    3. lazy initialized
    new value has been assigned to 'deleProperty' in test@7ef20235.
    4. test@7ef20235, thank you for delegating 'deleProperty' to me!
    
  • 原理解析
    by lazy中的lazy实际是一个生产Lazy<T>对象的工厂方法。它的源码如下:

    1
    
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    

    该方法接收lambda表达式,调用SynchronizedLazyImpl方法后,返回Lazy<T>对象,这个Lazy<T>就是被委托的对象。而SynchronizedLazyImpl的执行是同步锁的。如果不需要同步锁,可以传入LazyThreadSafetyMode线程模式来实现不同的初始化模式。 传入线程模式时的方法源码:

    1
    2
    3
    4
    5
    6
    
    public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
      when (mode) {
          LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
          LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
          LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
      }
    

    不论哪种初始化模式,在XXXLazyImpl方法中都实现了Lazy接口,并重写了其中value属性的get方法,也就是提供了新的getValue方法。 该方法规定:

    1. value未被初始化时,执行lambda表达式实施初始化
    2. value已被初始化,直接返回值
  • 应用场景
    除了lazy以外,安卓标准库还有实现了Lazy接口的其他方法,比如可以像这样把viewModel属性委托给Fragment.viewModels方法生产的对象:

    1
    
    val viewModel by viewModels<MyViewModel> { myFactoryProvider }
    

    这个看起来有点难理解,其实和上面lazy的原理是一样的。viewModels方法返回Lazy<VM>对象,该对象实现Lazy接口。这里的lambda表达式主要内容就是获得生产viewModel的工厂的返回值,用它来延迟初始化viewModel。 其中的myFactoryProvider代表的是参数factoryProducer: (() -> Factory)(kotlin中lambda表达式是最后一个参数的时候可以写到参数括号的外面),这个参数就是用来生产viewModel的工厂本厂啦。

3. 总结

  • kotlin中的委托也是委托模式的一种实现,语言提供的by关键字使得委托实现更为简便。
  • 通过by实现的委托主要有两种:1.委托接口的实现 2.委托属性的读写。
  • 委托接口实现可以减少实现类对接口方法的不必要的实现。
  • 委托属性读写提供getValue/setValue方法来替代属性原有的get/set方法。 标准库的lazy方法实现了Lazy接口。它提供的getValue方法使得只有第一次调用属性get方法时执行初始化代码块,后续调用则直接返回已初始化的值。