В Scala, как я могу использовать неявное преобразование, чтобы «добавить» методы в подклассы общего родителя? - PullRequest
3 голосов
/ 11 апреля 2019

Допустим, у меня есть некоторые данные в "глупых" моделях.В этом примере я буду использовать 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)
}

Я уверен, что есть название для того, что я ищу, но я просто не знаю, что это такое или что искатьдля.

1 Ответ

3 голосов
/ 11 апреля 2019

Проблема: компилятор не может выбирать и использовать неявное значение, специфичное для обработки подкласса.В принципе невозможно решить, какой метод вызывать (для Triangle или Circle), если известно, что это Shape.Это на самом деле классическая проблема, которая имеет стандартные решения.

Решение 1

Сопоставление шаблонов внутри DocumentExtension.add

Плюсы:

  1. Так как ваш trait Shape определен как sealed, компилятор вам, если вы пропустите случай для определенного предка.
  2. Разделение определения класса и обработки действия

Минусы:

  1. Boilerplate требуется перечислить все подклассы вашей черты

Решение 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, но требует добавления странных методов к самой черте.

...