Kotlin委托
委托(Delegation)
大多数编程工作都涉及重用已有代码,有时只做少量改动。在面向对象编程中(Kotlin 也是如此),代码复用的主要手段是继承(以及组合),我们之前已经讲过了。这节内容我们讲另一种替代继承的方式——委托。
委托的语法
委托是使用某个对象来完成接口的实现,而不是在当前类里重新写一遍实现。我们来看看具体怎么做。
假设我们有这样一段简单的代码——一个接口和它的实现:
interface MyInterface {fun print()val msg: String
}class MyImplementation : MyInterface {override fun print() {println(msg)}override val msg: String = "MyImplementation sends regards!"
}
解释:
接口声明了一个属性和一个方法,类 MyImplementation
实现了它们。
现在,假设我们想创建一个新类,这个类要:
-
拥有自己的功能,
-
同时实现上述接口。
如果直接用继承,可能需要复制粘贴已有代码;但用委托就能避免这种重复。
用委托写法:
class MyNewClass(base: MyInterface) : MyInterface by base {override val msg = "Delegate sends regards."
}
解释:
-
构造函数参数
base
期望传入一个MyInterface
的实现。 -
冒号后面写
MyInterface by base
,表示MyNewClass
实现了接口MyInterface
,但接口的具体实现“委托”给了base
。 -
msg
属性被重写成了新的值。
实例代码:
val delegate = MyImplementation()
val delegatingObj = MyNewClass(delegate)
println(delegatingObj.msg)
输出:
Delegate sends regards.
但是,如果调用 print() 呢?
delegatingObj.print()
它会打印:
MyImplementation sends regards!
解释:
虽然 MyNewClass
没有自己写 print()
方法,但它委托给了 base
,即 MyImplementation
的实例,调用的就是 MyImplementation
里的 print()
。而 print()
内部打印的是 MyImplementation
的 msg
,所以输出是 MyImplementation sends regards!
。
总结:
-
MyNewClass
中重写的成员会被使用。 -
没有重写的方法和属性则直接委托给
base
实现。
一个更复杂的例子 — 回调和日志器
现在,我们看一个包含两个委托的复杂例子。
-
ICallbackReceiver
:回调接口,支持在执行一个动作前后调用特定函数。 -
ILogger
:日志接口,负责格式化并输出日志。
// 回调接口
interface ICallbackReceiver {fun onBeforeAction()fun onAfterAction()fun action(function: () -> Unit) {onBeforeAction()function()onAfterAction()}
}// 日志接口
interface ILogger {fun getStubDateTime() = "05.11.2022-14:31:04" // 占位时间val format: Stringget() = "[${getStubDateTime()}]: "fun print(s: String)
}
实现:
// 简单的日志实现
class BasicLogger : ILogger {override fun print(s: String) = println(format + s)
}// 实现回调接口,日志功能委托给 logger
class ConsoleNotifier(logger: ILogger) : ICallbackReceiver, ILogger by logger {val onBeforeStr = "OnBefore!"val onAfterStr = "OnAfter!"override fun onBeforeAction() = print(onBeforeStr)override fun onAfterAction() = print(onAfterStr)
}
再定义一个既支持回调又支持日志的类,全部用委托完成:
class ExampleParser(notifier: ICallbackReceiver, logger: ILogger) :ICallbackReceiver by notifier,ILogger by logger {fun start() = action { parseFiles() }fun parseFiles() {print("Parsing...")// 这里写具体的解析逻辑}
}
运行示例:
fun main() {val loggerInstance = BasicLogger()val dateTimeNotifier = ConsoleNotifier(loggerInstance)val simpleParser = ExampleParser(dateTimeNotifier, loggerInstance)simpleParser.start()
}
输出:
[05.11.2022-14:31:04]: OnBefore!
[05.11.2022-14:31:04]: Parsing...
[05.11.2022-14:31:04]: OnAfter!
总结
委托极大提升了代码复用的便利性。相比复制粘贴已有代码,我们只需引入已有的功能对象,并把接口的实现委托给它即可。Kotlin 对委托的语言支持非常好,让这项工作简单且优雅。