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