Начнем с циклической зависимости.
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
Хотя, если вы переопределите член типа self, вы потеряете доступ к исходному элементу, к которому все еще можно получить доступ через супер, используя наследование. Итак, что действительно наработано с использованием наследования:
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 , поэтому его можно использовать как тип self, а затем смешать в end (при создании экземпляра). Однако val geomR
необходимо инициализировать, прежде чем использовать его, наследуя черты. Поэтому нам нужен класс для принудительной инициализации geomR
. Класс ScnVista
может затем наследоваться из-за множества ортогональных признаков, которые сами могут быть унаследованы. Использование нескольких параметров типа (generics) предлагает альтернативную форму модульности.