在 Scala 中,如何使用隐式转换到 "add" 方法到公共父类的子类?
In Scala, how can I use implicit conversion to "add" methods to subclasses of a common parent?
假设我在 "dumb" 个模型中有一些数据。在此示例中,我将使用 Circle
和 Triangle
,它们扩展了 trait Shape
.
我正在寻找一种方法来隔离可以使用这些形状的行为,但我不确定构建它的最佳方法。如果我试图将这些形状绘制到文档上,我希望能够编写如下所示的代码:
shapes.foreach(doc.add)
这里的技巧是 shapes
是 Seq[Shape]
,add
方法是我想隐式添加的东西,因为我不能修改形状本身(我也不会想将此特定功能融入其中)。
我遇到困难的地方是,我不知道如何将隐式转换与子类混合使用。有关详细信息,请参阅下面的代码 QUESTION:
。
// Let's assume I'm working with some shape models that are defined in some
// external library that's out of my control.
sealed trait Shape
case class Circle() extends Shape
case class Triangle() extends Shape
// Now I'm building an add that adds stuff to a Document
// and I want to locally implement methods that work on these general shapes.
case class Document()
// Using implicit conversion to add methods to a case class that's just holding data
implicit class DocumentExtensions(doc: Document) {
// I don't want this to be called
def add(shape: Shape): Unit = println("Add a shape")
// I want to use shape-specific methods
def add(shape: Circle): Unit = println("Add a circle")
def add(shape: Triangle): Unit = println("Add a triangle")
}
val doc = Document()
val shapes = Seq(Circle(), Triangle())
// This just prints "Add a shape" for the Circle and Triangle.
// I want to it to print "Add a circle" and "Add a triangle".
shapes.foreach { shape =>
// QUESTION:
// Is there a way or pattern to have this call the add for the
// subclass instead of for Shape? I want this to be fully dynamic
// so that I don't have to list out each subclass. Even more ideally,
// the compiler could warn me if there was a subclass that there wasn't
// an implicit add for.
doc.add(shape)
}
// This would work, but I'm wondering if there's a way to do this more
// dynamically without listing everything out.
shapes.foreach {
case c: Circle => doc.add(c)
case t: Triangle => doc.add(t)
}
我确定我要找的东西有一个名称,但我只是不知道它是什么或要搜索什么。
问题:编译器无法选择和使用特定于处理子class 的隐式值。当您只知道它是一个 Shape
时,基本上不可能决定调用什么方法(对于 Triangle
或 Circle
)。这实际上是一个 class 有标准解决方案的逻辑问题。
解决方案 1
里面的模式匹配DocumentExtension.add
优点:
- 因为你的
trait Shape
被定义为 sealed
,如果你错过了某个祖先的案例,编译器会帮你。
- 分离 class 定义和操作处理
缺点:
- 需要样板来列出你特质的所有子classes
解决方案 2
经典访客模式
sealed trait Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer)
}
final class Triangle extends Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer) = visitor.draw(doc, this)
}
final class Circle extends Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer) = visitor.draw(doc, this)
}
trait ShapeDrawer {
def draw(doc: Document, t: Circle)
def draw(doc: Document, t: Triangle)
}
val drawer: ShapeDrawer = ???
val doc: Document = ???
val shapes = Seq.empty[Shape]
shapes.foreach(_.addToDoc(doc, drawer))
此解决方案也满足在编译时确保您已处理 Shape 的每个子class 的要求,但需要向特征本身添加奇怪的方法。
假设我在 "dumb" 个模型中有一些数据。在此示例中,我将使用 Circle
和 Triangle
,它们扩展了 trait Shape
.
我正在寻找一种方法来隔离可以使用这些形状的行为,但我不确定构建它的最佳方法。如果我试图将这些形状绘制到文档上,我希望能够编写如下所示的代码:
shapes.foreach(doc.add)
这里的技巧是 shapes
是 Seq[Shape]
,add
方法是我想隐式添加的东西,因为我不能修改形状本身(我也不会想将此特定功能融入其中)。
我遇到困难的地方是,我不知道如何将隐式转换与子类混合使用。有关详细信息,请参阅下面的代码 QUESTION:
。
// Let's assume I'm working with some shape models that are defined in some
// external library that's out of my control.
sealed trait Shape
case class Circle() extends Shape
case class Triangle() extends Shape
// Now I'm building an add that adds stuff to a Document
// and I want to locally implement methods that work on these general shapes.
case class Document()
// Using implicit conversion to add methods to a case class that's just holding data
implicit class DocumentExtensions(doc: Document) {
// I don't want this to be called
def add(shape: Shape): Unit = println("Add a shape")
// I want to use shape-specific methods
def add(shape: Circle): Unit = println("Add a circle")
def add(shape: Triangle): Unit = println("Add a triangle")
}
val doc = Document()
val shapes = Seq(Circle(), Triangle())
// This just prints "Add a shape" for the Circle and Triangle.
// I want to it to print "Add a circle" and "Add a triangle".
shapes.foreach { shape =>
// QUESTION:
// Is there a way or pattern to have this call the add for the
// subclass instead of for Shape? I want this to be fully dynamic
// so that I don't have to list out each subclass. Even more ideally,
// the compiler could warn me if there was a subclass that there wasn't
// an implicit add for.
doc.add(shape)
}
// This would work, but I'm wondering if there's a way to do this more
// dynamically without listing everything out.
shapes.foreach {
case c: Circle => doc.add(c)
case t: Triangle => doc.add(t)
}
我确定我要找的东西有一个名称,但我只是不知道它是什么或要搜索什么。
问题:编译器无法选择和使用特定于处理子class 的隐式值。当您只知道它是一个 Shape
时,基本上不可能决定调用什么方法(对于 Triangle
或 Circle
)。这实际上是一个 class 有标准解决方案的逻辑问题。
解决方案 1
里面的模式匹配DocumentExtension.add
优点:
- 因为你的
trait Shape
被定义为sealed
,如果你错过了某个祖先的案例,编译器会帮你。 - 分离 class 定义和操作处理
缺点:
- 需要样板来列出你特质的所有子classes
解决方案 2
经典访客模式
sealed trait Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer)
}
final class Triangle extends Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer) = visitor.draw(doc, this)
}
final class Circle extends Shape {
def addToDoc(doc: Document, visitor: ShapeDrawer) = visitor.draw(doc, this)
}
trait ShapeDrawer {
def draw(doc: Document, t: Circle)
def draw(doc: Document, t: Triangle)
}
val drawer: ShapeDrawer = ???
val doc: Document = ???
val shapes = Seq.empty[Shape]
shapes.foreach(_.addToDoc(doc, drawer))
此解决方案也满足在编译时确保您已处理 Shape 的每个子class 的要求,但需要向特征本身添加奇怪的方法。