【发布时间】:2019-12-25 03:11:15
【问题描述】:
我不明白 Kotlin 中的 (BY) 关键字是这样的:
interface a {
}
class b():a {
}
class c(x:a):a by x {
}
【问题讨论】:
标签: kotlin delegation
我不明白 Kotlin 中的 (BY) 关键字是这样的:
interface a {
}
class b():a {
}
class c(x:a):a by x {
}
【问题讨论】:
标签: kotlin delegation
假设你已经阅读了docs,并且想要更多,我会试着给你我的解释……
为了说明为什么需要委托,让我们看看主要的替代方案。由于您的示例过于简单,无法演示这些问题,因此我们选择另一个示例:
假设您需要一个行为类似于(例如)stdlib LinkedList 的类,但添加了一些额外的行为。 (假设您需要记录添加到列表中的每个项目。)
执行此操作的传统方法是使用继承:创建LinkedList 的子类,并覆盖add() 方法:
class MyLinkedList<E> : LinkedList<E>() {
override fun add(e: E)
= super.add(e).also{ println(e) }
}
(我使用 Kotlin also() 函数来简化这一点。替代方法是调用超类方法,将结果存储在临时值中,进行日志记录,然后返回临时值……)
这个想法很简单。但是这种方法有不少隐藏的问题……
例如:如果您尝试过彻底,您还会发现还有其他方法可以将元素添加到列表中:还有第二个add() 方法可以让您指定位置。还有两个addFirst() 和两个addAll() 方法。所以你也覆盖了这些。
但随后您发现有时项目被记录两次......经过一些调查后,您发现了原因:在LinkedList 中,addAll() 方法之一的实现只是调用了另一个一。 (如果有人调用你的第一个 addAll() 方法,它会记录然后调用超类方法;它会调用另一个,但是因为你也重写了它,它会在调用 that 之前记录再次 超类方法。)
这是主要问题:您不仅需要知道要继承的类的公共接口,还需要知道其实现方式的私有细节。
对于 Java 标准库,您很幸运,因为 Oracle 发布了源代码。 (如果它是第三方库,您可能就没那么幸运了。)因此您可以通过仅覆盖两个 addAll() 方法之一来解决此问题。
但这不是一个合适的解决方案。如果 Oracle 在未来版本中更改实现会怎样? (这不是理论上的问题;它在实践中经常发生!)
这被称为fragile base class 问题,并且没有真正好的解决方法。 除非您也控制超类,否则子类化并不安全:很容易留下隐藏的问题,并且如果超类发生更改,您的代码也会中断。
那么有什么替代方法呢?委派。
您无需创建子类,而是编写List 接口的单独实现;您的类包含LinkedList 的实例,并且您的所有方法都只是调用该实例上的方法 - 除了add…() 方法,它们也可以记录您的日志,例如:
class MyLinkedList<E>(val delegate: LinkedList<E>) : List<E> {
fun add(e: E)
= delegate.add(e).also{ println(e) }
// ...similar for the other add methods...
override val size = delegate.size
override fun get(index: Int) = delegate.get(index)
// ...similar for all the remaining List methods...
}
这样更安全。您委托的LinkedList 是否调用它自己的方法并不重要;这不会对您的代码产生任何影响。您的课程与LinkedList 的内部细节以及对其的更改是绝缘的。太好了!
那么为什么不更频繁地使用委托呢?因为在 Java 和类似的语言中,它非常冗长。从上面可以看出,你不仅要编写你感兴趣的add…方法的实现;您还必须在接口中编写所有其他方法的实现,所有这些都只是将这些调用转发到您的委托实例。在每种情况下都有很多样板,但java.util.List 有大约 30 个公共方法! (当然,这引入了一种新的脆弱性:如果将任何新方法添加到接口中,您的代码将中断,直到您添加它们。)
非常痛苦。
但在 Kotlin 中不行! Kotlin 为您提供了委托的好处,而无需编写所有样板!你只需告诉它你正在实现什么接口,以及你想委托给什么实例,它就会自动生成所有必要的转发方法!您只需覆盖您想要的,其余的由它完成!
class MyLinkedList<E>(val delegate: LinkedList<E>) : List<E> by delegate {
fun add(e: E)
= delegate.add(e).also{ println(e) }
// ...similar for the other add methods...
}
因此,您可以两全其美:您可以编写与传统子类化方法一样简单和简洁的 Kotlin 代码,但具有委托的所有安全性和稳健性 - 并且不会在接口更改时中断,要么。
【讨论】: