由子类型

时间:2019-05-06 18:03:06

标签: scala generics

我正在尝试以一种干净的方式实现以下代码体系结构。

有一个特征(也可以是抽象类,但我认为它不能解决这个问题),我称之为Shape,该特征由两个类Circle和{ {1}}。它们每个都提供自定义成员:Shape具有一个(抽象的)方法Rectangle,该方法可以更改大小(由子类实现);圈子有一个size字段;并且矩形具有radiuswidth字段。

然后,有一个通用特征height,它可以对形状进行修改。它是通用的Changer,并通过三个不同的类进行子类型化:Changer[-S <: Shape]可以改变SizeChanger的大小,Shape可以改变{{1}的宽度}和WidthChanger来更改Rectangle的半径。

每个RadiusChanger都有一个方法Circle,该方法实际上执行Changer指定的更改。

最后,在change(shape)类中有一个方法Changer,基本上是用该对象调用形状为change(shapeChanger)的{​​{1}}方法。

总而言之,代码看起来像这样:

Shape

这些类的使用示例:

change

Changer性状trait Shape { def change(changer: /* This is the question */): Unit = { changer.change(this) } def size(s: Int): Unit } class Circle(var r: Int) extends Shape { override def size(s: Int): Unit = r = s/2 } class Rectangle(var w: Int, var h: Int) extends Shape { override def size(s: Int): Unit = { w = s h = s } } trait Changer[-S <: Shape] { def change(shape: S): Unit } class Size(s: Int) extends Changer[Shape] { override def change(shape: Shape): Unit = shape.size(s) } class Radius(r: Int) extends Changer[Circle] { override def change(shape: Circle): Unit = shape.r = r } class Width(w: Int) extends Changer[Rectangle] { override def change(shape: Rectangle): Unit = shape.w = w } 参数被指定为反变量,因为从技术上讲val circle = new Circle(42) val rectangle = new Rectangle(10, 20) val rectAsShape: Shape = rectangle val widthChanger = new Width(30) val radiusChanger = new Radius(17) val sizeChanger = new Size(40) val radAsChanger: Changer[Circle] = radiusChanger val sizeAsChanger: Changer[Shape] = sizeChanger val sizeAsCircleChanger: Changer[Circle] = sizeChanger circle.change(radiusChanger) circle.change(sizeChanger) circle.change(sizeAsCircleChanger) rectangle.change(widthChanger) rectangle.change(sizeChanger) rectangle.change(sizeAsChanger) rectAsShape.change(sizeChanger) // rectAsShape.change(widthChanger) // Shouldn't work (以及所有Changer)也是有效的S,因为他们知道来处理SizeChanger,例如Changer[Shape]

真正的问题是Changer[Circle]特性中的Shape方法。实际上,对于Circle的每个子类来说,它看起来总是相同的,并且只会调用更改器。因此,它应该是通用的,不要期望change,而应该是Shape,其中Shape是具体对象的类型。这样,Changer[Shape]可以接受更具体的Changer[S],我们仍然可以将其强制转换为S并使用Circle调用该方法。

我尝试了许多不同的类型参数组合,使用了各种方差和类型界限,但没有得到正确的组合。

幸运的是,我发现以下定义有效:

Changer[Circle]

但是我正在寻找一种更通用的方法。

我很确定这个问题是SO上其他问题的重复,因为在我看来这种模式很常见,但是由于我无法在该模式上命名,所以我没有找到任何有效或无用的东西(暂时)。

因此,在此代码模式下的任何名称(或对该代码的不良方面的任何看法)都将有所帮助。

编辑1:

也许我可以补充一点,最终目标是在最终看起来像这样的DSL中使用所有这些(通过Scala.js用于Canvas API)

Shape

因此,每次用户要更改形状的属性时都要求用户“创建”新变量很麻烦,因此必须在内部更改形状。

此外,Changer[Shape]方法将有某种返回类型而不是def change(changer: Changer[this.type]): Unit = ??? ,尽管我(好吧,我们)仍然需要弄清楚它。

1 个答案:

答案 0 :(得分:0)

好吧,我想我的回答会很冗长,但是既然您已经征求了代码的所有方面的意见,我希望这种解释将帮助您理解Scala方法的一般原理。

首先,在Scala中,通常会尝试避免任何可变的数据结构。这是为什么?让我们检查change类中Changer方法的签名。它需要一些东西(Shape并返回Unit。可以公平地假设ChangerShape本身不做任何事情,因为Unit除了“代码块已完成”之外没有任何信号。因此,第一步是将签名更改为change(shape: S): S以显示我们的意图。

好吧,现在我们显然正在进行一些修改,但是我们可以通过删除varCircle中的所有Rectangle并将它们设置为{{3 }}。案例类是Scala中用于定义不可变数据结构的简洁概念。我们无法更改其状态,但是可以使用新状态创建新的状态。定义很简单:

case class Circle(r: Int) extends Shape { ... }
case class Rectangle(w: Int, h: Int) extends Shape { ... }

案例类使我们可以使用方便的copy方法,您可以从我上面提供的链接中阅读该方法。因此,现在,当我们不需要突变任何Shape时,我们可以从基本特征中删除change方法。但是,当然,这意味着我们失去了方便的obj.change(...)语法。一个人该怎么处理?

答案是类型类模式。罗伯·诺里斯(Rob Norris)着case classes详细解释了该模式。而且我们已经拥有实现它的一切。我们只需要写一个简短的neat post,基本上是语法扩展。

我将提供最基本的方法:

implicit class ChangerOps[S <: Shape](s: S) {
    def change(implicit c: Changer[S]): S = c.change(s)
}

然后,您只需将SizeRadiusWidth声明为implicit val(您可以阅读有关隐式implicit class的信息)并使用语法刚刚写过。因为一次准备好here即可在线确认所有这些信息可能有点困难。请注意,这种方法省略了自变量,因为您可以扩展类型以获得所需的隐式。