简体   繁体   English

在Scala中,如何使用隐式转换将方法“添加”到公共父类的子类?

[英]In Scala, how can I use implicit conversion to “add” methods to subclasses of a common parent?

Let's say I have some data in "dumb" models. 假设我在“哑”模型中有一些数据。 In this example, I'll use Circle and Triangle , which extend a trait Shape . 在这个例子中,我将使用CircleTriangle ,它们扩展了一个trait Shape

I'm looking for a way to isolate behavior that could use these shapes, but I'm not sure the best way to structure it. 我正在寻找一种方法来隔离可能使用这些形状的行为,但我不确定构建它的最佳方法。 If I'm trying to draw these shapes onto a document, I'd want to be able to write code that looked like this: 如果我试图将这些形状绘制到文档上,我希望能够编写如下代码:

shapes.foreach(doc.add)

The trick here is that shapes is Seq[Shape] , and the add method is something I want to add implicitly since I can't modify the shapes themselves (nor would I want to bake this specific functionality into them). 这里的诀窍是shapesSeq[Shape] ,而add方法是我想要隐式添加的东西,因为我无法修改形状本身(我也不想将这些特定的功能融入其中)。

Where I'm getting stuck is, I don't know how to mix implicit conversions with the subclasses. 我遇到困难的地方是,我不知道如何将隐式转换与子类混合在一起。 See QUESTION: below in the code for more info. 有关详细信息,请参阅代码中的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)
}

I'm sure there's a name for what I'm looking for, but I just don't know what it is or what to search for. 我确定我正在寻找什么名称,但我不知道它是什么或搜索什么。

Problem: compiler cannot choose and use an implicit value specific to handle a subclass. 问题:编译器无法选择和使用特定于处理子类的隐式值。 It's basically impossible to decide what method to call (for Triangle or Circle ) when you only know that it's a Shape . 当你只知道它是一个Shape时,基本上不可能决定调用什么方法(对于TriangleCircle )。 This is actually a classical problem, which has standard solutions. 这实际上是一个经典问题,它有标准的解决方案。

Solution 1 解决方案1

Pattern matching inside DocumentExtension.add DocumentExtension.add模式匹配

Pros: 优点:

  1. Since your trait Shape is defined as sealed , compiler will you if you miss a case for a certain ancestor. 由于您的trait Shape被定义为sealed ,如果您错过了某个祖先的案例,编译器会给您。
  2. Separation of class definition and action handling 分类定义和动作处理

Cons: 缺点:

  1. Boilerplate required to list all subclasses of your trait 要求列出特征的所有子类的Boilerplate

Solution 2 解决方案2

Classical Visitor pattern 古典访客模式

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

This solution also matches the requirement of being sure at compile time that you've handled every subclass of Shape, but requires adding strange methods to the trait itself. 此解决方案还符合在编译时确保已处理Shape的每个子类的要求,但需要向特征本身添加奇怪的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM