При использовании наследования для достижения ad-hoc-полиморфизма нам может потребоваться существенное загрязнение интерфейса наших объектов значений.
Предположим, мы хотим реализовать Реальное и комплексное число. Без какой-либо функциональности, это так же просто, как написание
case class Real(value: Double)
case class Complex(real: Double, imaginary: Double)
Теперь предположим, что мы хотим реализовать добавление
- двух действительных чисел
- Реальный и комплексное число
- Два комплексных числа
Решение с использованием наследования (Редактировать: На самом деле, я не уверен, что это можно назвать наследованием, поскольку метод add
в чертах не имеет реализации. Однако, в этом отношении пример ничем не отличается от примера Эрика Орхейма в) может выглядеть следующим образом:
trait AddableWithReal[A] {
def add(other: Real): A
}
trait AddableWithComplex[A] {
def add(other: Complex): A
}
case class Real(value: Double) extends AddableWithComplex[Complex] with AddableWithReal[Real] {
override def add(other: Complex): Complex = Complex(value + other.real, other.imaginary)
override def add(other: Real): Real = Real(value + other.value)
}
case class Complex(real: Double, imaginary: Double) extends AddableWithComplex[Complex] with AddableWithReal[Complex] {
override def add(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary)
override def add(other: Real): Complex = Complex(other.value + real, imaginary)
}
Поскольку реализация оного тесно связана с Real
и Complex
, мы должны увеличить их интерфейсы каждый раз добавляется новый тип (например, целые числа) и каждый раз, когда требуется новая операция (например, вычитание).
Классы классов обеспечивают один из способов отделить реализацию от типов.Например, мы можем определить черту
trait CanAdd[A, B, C] {
def add(a: A, b: B): C
}
и отдельно осуществлять добавление с использованием implicits
object Implicits {
def add[A, B, C](a: A, b: B)(implicit ev: CanAdd[A, B, C]): C = ev.add(a, b)
implicit object CanAddRealReal extends CanAdd[Real, Real, Real] {
override def add(a: Real, b: Real): Real = Real(a.value + b.value)
}
implicit object CanAddComplexComplex extends CanAdd[Complex, Complex, Complex] {
override def add(a: Complex, b: Complex): Complex = Complex(a.real + b.real, a.imaginary + b.imaginary)
}
implicit object CanAddComplexReal extends CanAdd[Complex, Real, Complex] {
override def add(a: Complex, b: Real): Complex = Complex(a.real + b.value, a.imaginary)
}
implicit object CanAddRealComplex extends CanAdd[Real, Complex, Complex] {
override def add(a: Real, b: Complex): Complex = Complex(a.value + b.real, b.imaginary)
}
}
Такое разделение имеет по крайней мере два преимущества
- Предотвращение загрязнения интерфейсов
Real
и Complex
- Позволяет вводить новые
CanAdd
-функциональность Хет возможность изменять исходный код классов, которые могут быть добавлены
Например, мы можем определить CanAdd[Int, Int, Int]
добавить два Int
значения без изменения Int
класса:
implicit object CanAddIntInt extends CanAdd[Int, Int, Int] {
override def add(a: Int, b: Int): Int = a + b
}
Что делать, если вы хотите добавить 'Int' в вашу семью' Foo'? Как ты делаешь это? –
'class FooInt расширяет Int с помощью Foo' ?? –
@ Wei-ChingLin Это даст ошибку: незаконное наследование от конечного класса Int' – Kolmar