Подтвердить что ты не робот

Модульный дизайн Scala: как я могу избежать конструктора "push-out" шаблона?

Возможно, эксперт Scala с хорошим чувством стиля и элегантности может помочь мне разобраться в более удобном способе структурирования следующего кода, который имеет конструкторскую проблему "push-out".

Начнем с простого базового класса:

class Foo(val i: Int, val d: Double, val s: String) {

  def add(f: Foo) = new Foo(i + f.i, d + f.d, s + f.s)
  override def toString = "Foo(%d,%f,%s)".format(i,d,s)

}

Для целей проверки типов в сложном приложении мне нужен подкласс без какого-либо дополнительного состояния:

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}

Как бы то ни было, когда я добавляю два бара, я возвращаюсь только к Foo:

val x = new Bar(1,2.3,"x")
val y = new Bar(4,5.6,"y")
val xy = x.add(y)

со следующим ответом в REPL:

x  : Bar = Bar(1,2.300000,x)
y  : Bar = Bar(4,5.600000,y)
xy : Foo = Foo(5,7.900000,xy)

Как мне добавить два бара для объединения друг с другом, чтобы сформировать еще один Bar (а не Foo), элегантно, без необходимости копировать и вставлять Foo add method, как показано ниже?

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  // ugly copy-and-paste from Foo:
  def add(b: Bar) = new Bar(i + b.i, d + b.d, s + b.s)
  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}

У меня много таких баров (все по существу копии Foo, но очень важные для проверки типов), решение без вырезания и пасты выплатит дивиденды.

Спасибо!

4b9b3361

Ответ 1

Я стараюсь избегать наследования как можно больше. Итак, вот альтернативный подход.

Класс Bar имеет точно такой же конструктор, что и Foo, и оба являются апатридами. Если вы хотите иметь несколько подтипов, просто чтобы передать любую дополнительную информацию, вы можете использовать общий параметр как "метку". Например:

trait Kind
trait Bar extends Kind

class Foo[T<:Kind](val i: Int, val d: Double, val s: String) {
   def add(f: Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
   override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}


scala> val b1 = new Foo[Bar](2,3.0,"hello")
b1: Foo[Bar] = Foo(2,3.000000,hello)

scala> val b2 = new Foo[Bar](3,1.0," world")
b2: Foo[Bar] = Foo(3,1.000000, world)

scala> b1 add b2
res1: Foo[Bar] = Foo(5,4.000000,hello world)

Теперь add безопасен по типу. Затем вы можете использовать класс типа, чтобы получить toString для отображения Kind.

Ответ 2

Развернувшись на @paradigmatic ответе, если вы хотите поддерживать операции, специфичные для каждого Bar (например, разные toString), вы можете сделать еще один шаг и сделать Kind класс.

trait Kind[T] { def name : String }
trait Bar
implicit object BarHasKind extends Kind[Bar] { val name = "Bar" }

class Foo[T : Kind](val i : Int, val d : Double, val s : String) {
  def add(f : Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
  override def toString = implicitly[Kind[T]].name + "(%d,%f,%s)".format(i,d,s)
}

scala> val b1 = new Foo[Bar](2, 3.0, "hello")
b1: Foo[Bar] = Bar(2,3.000000,hello)

trait Biz
implicit object BizHasKind extends Kind[Biz] { val name = "Biz" }

scala> val b2 = new Foo[Biz](1, 1.0, "One")

Это безопасно, как и раньше:

scala> b1 add b2
<console>:16: error: type mismatch;
  found   : Foo[Biz]
  required: Foo[Bar]

scala> b2 add b2
resN: Foo[Biz] = Biz(2,2.000000,OneOne)

Для любого свойства, которое вы хотите зависеть от тега, объявите их абстрактно в Kind и предоставите реализации в неявных объектах.

Ответ 3

Подход с параметризацией типа имеет ограничение в том, что он не позволяет вам естественным образом расширять функциональность, которая является наследованием. Таким образом, альтернативный подход может быть переопределяемым методом factory (который просто является делегатом для конструктора):

class Foo(val i: Int, val d: Double, val s: String) {

  protected def create(i: Int, d: Double, s: String) = new Foo(i, d, s)

  def add[A <: Foo](f: A) = create(i + f.i, d + f.d, s + f.s)

  override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  protected override def create(i: Int, d: Double, s: String) = new Bar(i, d, s)

  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

  // additional methods...

}

println( new Foo(10, 10.0, "10") add new Bar(10, 10.0, "10") )
println( new Bar(10, 10.0, "10") add new Foo(10, 10.0, "10") )