【问题标题】:Inherit class with chaining functions使用链接函数继承类
【发布时间】:2020-01-02 14:11:00
【问题描述】:

假设我有一个带有链接函数的类

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
  }
}

所以每个方法都返回实例本身

现在我想继承 Vehicle 并向新类添加一些方法

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

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

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

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

2 明显的解决方法: 要么使用 asInstanceOf

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

在新类中覆盖每个父方法

两种方式都不好看,我宁愿把所有这些类型的问题留给 Vehicle 类并忘记它,所以我找到了(如我所愿)解决方案,重写这样的方法

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

所以我希望返回类型将取自方法的第二个参数车辆,它始终等于默认值“this”,并将返回当前类型的值。不过很遗憾

new Helicopter().moveLeft(10)

仍然返回 Vehicle 类型的值 res0: Vehicle = Helicopter@1de81c37

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

谢谢

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

【问题讨论】:

  • 这被称为返回电流类型问题。有两种众所周知的解决方案,F-Bounded 多态性和typeclasses。检查this 以深入讨论解决此问题的两种技术。还有第三种选择使用 Type members,它类似于 F-Bounded 但更弱。最后,对于这种特定情况,由于您正在改变一个值而不是返回一个修改后的副本 (这应该是理想的) 我相信您可以使用 this.type 作为返回类型。所以def moveLeft(...): this.type.
  • 谢谢,我会读...告诉某人“应该是理想的”的真正理由

标签: scala inheritance method-chaining


【解决方案1】:

如果方法总是返回this,你可以给它返回类型this.type

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

【讨论】:

  • 这么简单?谢谢!但是你能解释一下为什么我的方法不起作用,我可以更好地理解 scala 类型机制吗?
  • @DmitryReutov 是否有理由通过 vehicle: T = this 重写 moveLeft ?如果你没有通过,那么它将作为val heli: Helicopter = new Helicopter().moveLeft[Helicopter](10).flyIntoTheSky(100)
  • @asanand,是的,但我不想这样写 moveLeft[Helicopter],正如我提到的,我想把它留给 Vehicle 类并忘记
  • @DmitryReutov 当您调用 new Helicopter().moveLeft(10) 时,T 的类型仍然是 Vehicle。您可以通过关注此帖子 scala-lang.org/old/node/10493 获得 T 类型。基本上这就是我测试的方式(按照上面相同的链接):def moveLeft[T](meters: Int, vehicle: T = this)(implicit m: ClassManifest[T]): T = { position = position._1 - meters -> position._2 println(m.erasure) this.asInstanceOf[T] }
  • @asanand,奇怪,我无法理解。如果 this.type 指 Helicopter 为什么 def moveLeft[T](meters: Int, vehicle: T = this): T 仍然指 Vehicle
【解决方案2】:

处理评论

奇怪,我无法理解。如果 this.type 指的是 Helicopter 为什么 def moveLeft[T](meters: Int, vehicle: T = this): T 还是指 Vehicle

请注意thisthis.type 存在于两个不同的世界中,尽管this.type 正在“侵犯”价值世界,我将在下面尝试解释。前者是一个value,而后者是一个type,虽然是一种特殊的类型。 this的表达式类型不是this.type,而是根据SLS

表达式this... 代表由最内层定义的对象 模板或复合类型封闭引用。如果这是一个 复合类型,this 的类型就是那个复合类型。如果它是一个 具有简单名称 ? 的类或对象定义的模板,类型 这与?.this的类型相同。

def moveLeft[T](meters: Int, vehicle: T = this): Tthis 的最内层封闭类实际上是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 似乎会根据值“改变”,但它仍然是一个 static 类型,这意味着没有运行时类型检查的恶作剧。

我相信混淆的根源在于我们知道,鉴于new Helicopter(),在这两种情况下,this 在运行时都指代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))

【讨论】:

    猜你喜欢
    • 2011-07-17
    • 2014-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多