Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

类委托

组合和继承都将子对象放置在新类中。使用组合时,子对象是显式的;使用继承时,子对象是隐式的。

组合使用嵌入对象的功能,但不暴露其接口。如果一个类需要重用现有实现并实现其接口,您有两个选择:继承和类委托

类委托位于继承和组合之间。与组合类似,您将一个成员对象放置在正在构建的类中。与继承类似,类委托暴露了子对象的接口。此外,您可以向上转型为成员类型。对于代码重用,类委托使得组合具有与继承相同的强大功能。

如果没有语言支持,您将如何实现这一点?在这里,一个太空飞船需要一个控制模块:

// ClassDelegation/SpaceShipControls.kt
package classdelegation

interface Controls {
  fun up(velocity: Int): String
  fun down(velocity: Int): String
  fun left(velocity: Int): String
  fun right(velocity: Int): String
  fun forward(velocity: Int): String
  fun back(velocity: Int): String
  fun turboBoost(): String
}

class SpaceShipControls : Controls {
  override fun up(velocity: Int) =
    "up $velocity"
  override fun down(velocity: Int) =
    "down $velocity"
  override fun left(velocity: Int) =
    "left $velocity"
  override fun right(velocity: Int) =
    "right $velocity"
  override fun forward(velocity: Int) =
    "forward $velocity"
  override fun back(velocity: Int) =
    "back $velocity"
  override fun turboBoost() = "turbo boost"
}

如果我们想要扩展控制的功能或调整一些命令,我们可能会尝试从 SpaceShipControls 继承。但这不起作用,因为 SpaceShipControls 不是 open

要暴露 Controls 中的成员函数,您可以创建一个 SpaceShipControls 的实例作为属性,并将所有暴露的成员函数显式地委托给该实例:

// ClassDelegation/ExplicitDelegation.kt
package classdelegation
import atomictest.eq

class ExplicitControls : Controls {
  private val controls = SpaceShipControls()
  // 手动委托:
  override fun up(velocity: Int) =
    controls.up(velocity)
  override fun back(velocity: Int) =
    controls.back(velocity)
  override fun down(velocity: Int) =
    controls.down(velocity)
  override fun forward(velocity: Int) =
    controls.forward(velocity)
  override fun left(velocity: Int) =
    controls.left(velocity)
  override fun right(velocity: Int) =
    controls.right(velocity)
  // 修改的实现:
  override fun turboBoost(): String =
    controls.turboBoost() + "... boooooost!"
}

fun main() {
  val controls = ExplicitControls()
  controls.forward(100) eq "forward 100"
  controls.turboBoost() eq
    "turbo boost... boooooost!"
}

这些函数被转发到底层的 controls 对象,由于生成的接口与普通继承的接口相同,因此结果也相同。您还可以提供实现更改,就像 turboBoost() 一样。

Kotlin 自动化了类委托的过程,因此与 ExplicitDelegation.kt 中编写的显式函数实现不同,您只需指定一个要用作委托的对象。

要委托给一个类,请在接口名称后面放置 by 关键字,然后是要用作委托的成员属性:

// ClassDelegation/BasicDelegation.kt
package classdelegation

interface AI
class A : AI

class B(val a: A) : AI by a

将其阅读为“类 B 通过使用成员对象 a 来实现接口 AI”。您只能委托给接口,所以不能说 A by a。委托对象(a)必须是构造函数的参数。

现在,ExplicitDelegation.kt 可以使用 by 进行重写:

// ClassDelegation/DelegatedControls.kt
package classdelegation
import atomictest.eq

class DelegatedControls(
  private val controls: SpaceShipControls =
    SpaceShipControls()
): Controls by controls {
  override fun

 turboBoost(): String =
    "${controls.turboBoost()}... boooooost!"
}

fun main() {
  val controls = DelegatedControls()
  controls.forward(100) eq "forward 100"
  controls.turboBoost() eq
    "turbo boost... boooooost!"
}

当 Kotlin 看到 by 关键字时,它会生成与我们为 ExplicitDelegation.kt 编写的代码类似的代码。委托后,可以通过外部对象访问成员对象的函数,而无需编写所有额外的代码。

Kotlin 不支持多类继承,但可以使用类委托模拟它。通常,多继承用于组合具有完全不同功能的类。例如,假设您想通过将在屏幕上绘制矩形的类与管理鼠标事件的类结合起来,来生成一个按钮:

// ClassDelegation/ModelingMI.kt
package classdelegation
import atomictest.eq

interface Rectangle {
  fun paint(): String
}

class ButtonImage(
  val width: Int,
  val height: Int
): Rectangle {
  override fun paint() =
    "painting ButtonImage($width, $height)"
}

interface MouseManager {
  fun clicked(): Boolean
  fun hovering(): Boolean
}

class UserInput : MouseManager {
  override fun clicked() = true
  override fun hovering() = true
}

// 即使我们将类定义为 open,我们
// 仍会得到一个错误,因为一个超类列表中只能出现一个类:
// class Button : ButtonImage(), UserInput()

class Button(
  val width: Int,
  val height: Int,
  var image: Rectangle =
    ButtonImage(width, height),
  private var input: MouseManager = UserInput()
): Rectangle by image, MouseManager by input

fun main() {
  val button = Button(10, 5)
  button.paint() eq
    "painting ButtonImage(10, 5)"
  button.clicked() eq true
  button.hovering() eq true
  // 可以向上转型为两种委托类型:
  val rectangle: Rectangle = button
  val mouseManager: MouseManager = button
}

Button 实现了两个接口:RectangleMouseManager。它不能继承 ButtonImageUserInput 的实现,但它可以委托给它们两个。

请注意,构造函数参数列表中的 image 的定义既是 public 又是 var。这允许客户程序员动态替换 ButtonImage

main() 中的最后两行显示了 Button 可以向上转型为其两种委托类型。这就是多继承的目标,因此委托有效地解决了多继承的需求。

  • -

继承可能会受到限制。例如,如果超类不是 open,或者如果您的新类已经扩展了另一个类,您无法继承一个类。类委托使您摆脱了这些和其他限制。

谨慎使用类委托。在继承、组合和类委托这三个选择中,首先尝试组合。这是最简单的方法,可以解决大多数用例。在需要创建类型层次结构以在类型之间建立关系时,继承是必需的。当这些选项不适用时,类委托可以发挥作用。

练习和解答可以在 www.AtomicKotlin.com 找到。