使用链接函数继承 class

Inherit class with chaining functions

假设我有一个 class 带有链接函数

class Vehicle {
  protected var position: (Int, Int) = (0, 0)
  def moveLeft(meters: Int): Vehicle = {
    position = position._1 - meters -> position._2
    this
  }

  def moveForward(meters: Int): Vehicle = {
    position = position._1 -> (position._2 + meters)
    this
  }
}

所以每个方法 return 实例本身

现在我想继承 Vehicle 并添加一些方法到 new class

class Helicopter extends Vehicle {
  protected var verticalDimension: Int = 0
  def flyIntoTheSky(meters: Int): Helicopter = {
    verticalDimension += meters
    this
  }
}

如果我将创建一个新的 class 实例并从父实例调用任何函数,例如

new Helicopter().moveLeft(10).moveForward(20)

我将无法调用新的 class 方法 flyIntoTheSky,因为方法 return 类型是 Vehicle,而 Vehicle 对 Helicopter 的方法一无所知。

2 种显而易见的解决方法: 使用 asInstanceOf

new Helicopter()
  .moveLeft(10)
  .moveForward(20).asInstanceOf[Helocopter]
  .flyIntoTheSky(10000)

在新的 class

中覆盖每一个和每一个父方法

这两种方式看起来都不太好,我宁愿将所有此类问题留给 Vehicle class 并忘记它,所以我找到了(正如我所希望的)解决方案,重写这样的方法

def moveLeft[T](meters: Int, vehicle: T = this): T = {
  position = position._1 - meters -> position._2
  this.asInstanceOf[T]
}

所以我预计 return 类型将从方法的第二个参数 vehicle 中获取,它始终等于默认值 "this",并将 return 当前类型的值。但不幸的是

new Helicopter().moveLeft(10)

仍然是 returns 类型 Vehicle 的值 res0: 车辆 = 直升机@1de81c37

所以第一个问题:为什么它没有像我预期的那样工作。第二个:有没有什么漂亮的方法可以解决这个问题

谢谢

PS 在 google 我在 Java 找到了这个解决方案 https://www.andygibson.net/blog/article/implementing-chained-methods-in-subclasses/ , 但我不知道 java 并且无法在 scala 中翻译它

如果方法总是 returns this 你可以给它 return 输入 this.type.

def moveLeft(meters: Int): this.type = {
  position = position._1 - meters -> position._2
  this
}

处理评论

strange, i cant get sense. If this.type refers to Helicopter why def moveLeft[T](meters: Int, vehicle: T = this): T refers still to Vehicle

请注意,thisthis.type 存在于两个独立的世界中,尽管 this.type 在价值世界中是 "encroaching",正如我将在下面解释的那样。前者是 value 而后者是 type,尽管是一种特殊的类型。表达式this的类型不是this.type,而是根据SLS:

The expression this... stands for the object being defined by the innermost template or compound type enclosing the reference. If this is a compound type, the type of this is that compound type. If it is a template of a class or object definition with simple name </code>, the type of this is the same as the type of <code>.this.

def moveLeft[T](meters: Int, vehicle: T = this): Tthis最内层的class实际上是Vehicle,所以this的静态类型是Vehicle .

现在考虑类型 this.type:

    this           .           type
     |             |            |
    value     dot notation     type 
   \                               /
    -------------------------------
                   |
           value-dependent type

注意在 上使用的点符号 .,也就是说,如果 this 不是一个值,那么我们将使用 # 这样的符号 this#type。然而,它确实是一个值,这意味着类型 type 取决于 this。现在,它取决于什么值?好吧,没有双关语的意思,这取决于。给定表达式 new Helicopter() 它取决于 new Helicopter() 引用的值(对象),而给定表达式 new Car() 它取决于表达式 new Car() 引用的值。这里要理解的重要一点是,尽管现在 this.type 似乎是 "changing" 取决于一个值,但它仍然是 static 类型,这意味着有没有运行时类型检查恶作剧。

我相信混淆的根源在于我们知道,给定 new Helicopter(),在这两种情况下,this 指的是 class Helicopter 的对象运行时,但是关键是了解编译器可以确定哪些静态类型,这反过来会产生哪些成员是可调用的。在 this.type 的情况下,编译器可以确定最窄的可能值依赖 singleton type 唯一地被这个值所占据。


作为旁注,我尝试使用无形镜片:

import shapeless._
case class Position(x: Double, y: Double, z: Double = 0)

sealed trait Vehicle
case class Car(p: Position) extends Vehicle
case class Plane(p: Position) extends Vehicle

object Vehicle {
  implicit val carPosLens = lens[Car].p
  implicit val planePosLens = lens[Plane].p
}

implicit class Move[T <: Vehicle](v: T) {
  def moveX(d: Double)(implicit ev: Lens[T, Position]): T = ev.modify(v)(p => p.copy(x = p.x + d))
  def moveY(d: Double)(implicit ev: Lens[T, Position]): T = ev.modify(v)(p => p.copy(y = p.y + d))
}

implicit class Fly[T <: Plane](v: T) {
  def moveZ(d: Int)(implicit ev: Lens[T, Position]): T = ev.modify(v)(p => p.copy(z = p.z + d))
}

Plane(Position(0,0,0)).moveX(42).moveY(-3.142).moveZ(11)
Car(Position(0,0)).moveX(1)

输出

res0: Plane = Plane(Position(42.0,-3.142,11.0))
res1: Car = Car(Position(1.0,0.0,0.0))