Почнемо з циклічної залежності.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Однак модульність цього рішення не настільки велика, як це може здатися вперше, тому що ви можете переосмислити власні типи таким чином:
trait A1 extends A {
selfA1: B =>
override def fb = "B's String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A's String" }
val myObj = new A1 with B1
Хоча, якщо ви перекриєте члена типу самоврядування, ви втрачаєте доступ до початкового члена, до якого все ще можна отримати доступ через супер, використовуючи успадкування. Отже, що насправді отримується за рахунок використання спадщини:
trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1
Зараз я не можу стверджувати, що розумію всі тонкощі схеми торта, але мені вражає, що основний метод забезпечення модульності - це композиція, а не типи успадкування або самовизначення.
Версія успадкування коротша, але головна причина, чому я віддаю перевагу успадковуванню над типами самозрозуміння, полягає в тому, що я вважаю набагато складнішим правильний порядок ініціалізації правильним із типами self. Однак є деякі речі, які ви можете зробити із типами самозабезпечення, які ви не можете зробити із спадщиною. Типи самоврядування можуть використовувати тип, тоді як для успадкування потрібна ознака або клас, як у:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Ви навіть можете зробити:
trait TypeBuster
{ this: Int with String => }
Хоча ви ніколи не зможете це створити. Я не бачу жодної абсолютної причини для неможливості успадкування від типу, але я, безумовно, вважаю, що було б корисно мати класи конструкторів контурів і риси, як у нас є риси / класи конструктора типу. Як на жаль
trait InnerA extends Outer#Inner //Doesn't compile
У нас це є:
trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }
Або це:
trait Outer
{ trait Inner }
trait InnerA
{this: Outer#Inner =>}
trait InnerB
{this: Outer#Inner =>}
trait OuterFinal extends Outer
{ val myVal = new InnerA with InnerB with Inner }
Один момент, якому слід більше співпереживати, - це те, що риси можуть поширювати класи. Дякуємо Девіду Макльверу за те, що вказав на це. Ось приклад з мого власного коду:
class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
ScnBase
успадковується від класу Swing Frame, тому він може бути використаний як тип власного типу, а потім змішаний у кінці (при встановленні). Однак val geomR
його потрібно ініціалізувати перед тим, як його використовувати, успадковуючи риси. Тож нам потрібен клас для виконання попередньої ініціалізації geomR
. Потім клас ScnVista
може бути успадкований за допомогою декількох ортогональних ознак, від яких вони можуть бути успадковані. Використання декількох параметрів типу (generics) пропонує альтернативну форму модульності.