请给我解释一下科特林的班级代表团

时间:2019-08-19 22:13:00

标签: kotlin delegation

我不这样理解Kotlin中的(BY)关键字:

interface a {
}

class b():a {
}

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

1 个答案:

答案 0 :(得分:2)

假设您已经阅读过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()方法,该方法会先记录然后调用超类方法;该方法会调用另一个方法,但是由于您也覆盖了该方法,因此它会再次记录 ,然后再调用那个超类方法。)

这是主要问题:您不仅需要知道您要从其继承的类的公共接口,还需要知道其实现方式的私有细节。

对于Java stdlib,您很幸运,因为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为您带来的好处是,委托 无需编写所有样板文件!您只需告诉它您要实现的接口以及要委派的实例,它就会自动生成所有必要的转发方法!您只需覆盖所需的内容,其余的就完成了!

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代码,但具有委托的所有安全性和鲁棒性-如果界面更改,则不会中断,要么。