【问题标题】:Please explain to me class delegation in Kotlin [closed]请向我解释 Kotlin 的班级代表团[关闭]
【发布时间】:2019-12-25 03:11:15
【问题描述】:

我不明白 Kotlin 中的 (BY) 关键字是这样的:

interface a {
}

class b():a {
}

class c(x:a):a by x {
}

【问题讨论】:

标签: kotlin delegation


【解决方案1】:

假设你已经阅读了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 代码,但具有委托的所有安全性和稳健性 - 并且不会在接口更改时中断,要么。

【讨论】:

  • 写得很好!
猜你喜欢
  • 2015-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-20
  • 1970-01-01
  • 2015-01-06
  • 2016-10-05
  • 1970-01-01
相关资源
最近更新 更多