Scala 泛型子类型由子类型使用时泛型方法的类型参数

Scala 泛型子类型由子类型使用时泛型方法的类型参数,scala,generics,Scala,Generics,我试图以一种干净的方式实现以下代码体系结构 有一个特性(也可能是一个抽象类,但我不认为它能解决这个问题),我称之为Shape,它由两个类组成,Circle和Rectangle。它们中的每一个都提供了自定义成员:形状有一个(抽象)方法size,该方法允许更改大小(并由子类实现);圆有一个半径字段;矩形具有宽度和高度字段 然后,还有一个通用特性,Changer,它允许对形状进行修改。它是通用的,Changer[-S好吧,我想我的回答会很长,但是既然你已经询问了对代码所有方面的意见,我希望这个解释能帮

我试图以一种干净的方式实现以下代码体系结构

有一个特性(也可能是一个抽象类,但我不认为它能解决这个问题),我称之为
Shape
,它由两个类组成,
Circle
Rectangle
。它们中的每一个都提供了自定义成员:形状有一个(抽象)方法
size
,该方法允许更改大小(并由子类实现);圆有一个
半径
字段;矩形具有
宽度
高度
字段


然后,还有一个通用特性,
Changer
,它允许对形状进行修改。它是通用的,
Changer[-S好吧,我想我的回答会很长,但是既然你已经询问了对代码所有方面的意见,我希望这个解释能帮助你理解Scala方法的一般原理

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

好的,现在我们显然有一些修改正在进行中,但是我们可以通过从
矩形
中删除所有
var
并制作它们来进一步改进代码。在Scala中,Case类是定义不可变数据结构的一个简洁概念。我们不能更改它们的状态,但我们可以使用ne创建新的w州。定义非常简单:

case class Circle(r: Int) extends Shape { ... }
case class Rectangle(w: Int, h: Int) extends Shape { ... }
Case类允许我们使用handy
copy
方法,您可以从我上面提供的链接中阅读该方法。因此,现在,当我们不需要对任何
Shape
进行任何变异时,我们可以从基本特征中删除
change
方法。但是,当然,这意味着我们失去了handy
obj.change(…)
语法。如何处理这个问题

答案是类型类模式。Rob Norris的一篇文章详细解释了这个模式。我们已经有了实现它的一切。我们只需要写一篇简短的文章,基本上是一个语法扩展

我将为您提供最基本的解决方案:

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

implicit class ChangerOps[S您可能可以使用F-有界多态性来解决这个问题,类似于
trait Shape[S def change(changer:changer[S]):Unit=…
。但是,也许类型类方法会更好,但是如果您不修改当前形状,而是返回一个新的形状,那么效果会更好。请参阅以供参考。@LuisMiguelMejíaSuárez这些确实是我一直在寻找的概念,谢谢!:)我编辑了这个问题来解释为什么
形状必须在我的使用案例。首先,非常感谢您花了这么多时间来回答,通过您的示例和Rob Norris的博客文章,我学到了很多!我非常理解Scala强调的是一种不变的方法。在“我的”用例中,
Shape
s必须是可变的,因为它们在以非声明性方式编写的DSL中使用,不幸的是;我们可以重新设计它,使所有内容都是声明性的,但这可能不会给最终用户带来什么好处。我编辑了这篇文章,在脑海中添加了DSL的一个示例用法。而
change
实际上会返回有些事情(仍有待决定)。@Jimvy,事实上,您正在为Scala.js编写DSL是一个重要的补充,因为在许多情况下,我们在编写Scala.js代码时无法推断Scala的方法。但是,在这种情况下,我鼓励您决定DSL用于执行许多突变的程序(我的意思是,很多)使用DSL。如果是这样的话,那么也许你应该转向更注重性能的东西。相反,我认为每次生成新对象都很好。还要注意的是,当你通过使用DSL实现可读性并使代码更具描述性时,你通过使对象相互关联来增加处理它所需的认知负载这是因为每次你对对象进行变异时,你都需要考虑使用它的所有地方,并推断你的变异“这里”是否会破坏“那里”的程序。你提出的DSL非常简洁,我相信它只会从使用不变的数据中受益。
def change(changer: Changer[this.type]): Unit = ???
circle1 and circle2 change Radius(10)
circle1 and circle2 and rectangle1 change StrokeColor(Color.red) and StrokeWidth(4)
// This changes the 3 shapes' stroke color and stroke width.
case class Circle(r: Int) extends Shape { ... }
case class Rectangle(w: Int, h: Int) extends Shape { ... }
implicit class ChangerOps[S <: Shape](s: S) {
    def change(implicit c: Changer[S]): S = c.change(s)
}