Ответ 1
При использовании наследования для достижения 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)
}
Поскольку реализация add тесно связана с 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
}