我正在尝试以一种干净的方式实现以下代码体系结构。
有一个特征(也可以是抽象类,但我认为它不能解决这个问题),我称之为Shape
,该特征由两个类Circle
和{ {1}}。它们每个都提供自定义成员:Shape具有一个(抽象的)方法Rectangle
,该方法可以更改大小(由子类实现);圈子有一个size
字段;并且矩形具有radius
和width
字段。
然后,有一个通用特征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 = ???
,尽管我(好吧,我们)仍然需要弄清楚它。
答案 0 :(得分:0)
好吧,我想我的回答会很冗长,但是既然您已经征求了代码的所有方面的意见,我希望这种解释将帮助您理解Scala方法的一般原理。
首先,在Scala中,通常会尝试避免任何可变的数据结构。这是为什么?让我们检查change
类中Changer
方法的签名。它需要一些东西(Shape
并返回Unit
。可以公平地假设Changer
对Shape
本身不做任何事情,因为Unit
除了“代码块已完成”之外没有任何信号。因此,第一步是将签名更改为change(shape: S): S
以显示我们的意图。
好吧,现在我们显然正在进行一些修改,但是我们可以通过删除var
和Circle
中的所有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)
}
然后,您只需将Size
,Radius
,Width
声明为implicit val
(您可以阅读有关隐式implicit class的信息)并使用语法刚刚写过。因为一次准备好here即可在线确认所有这些信息可能有点困难。请注意,这种方法省略了自变量,因为您可以扩展类型以获得所需的隐式。