Vamos começar com a dependência cíclica.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
No entanto, a modularidade dessa solução não é tão boa quanto parece à primeira vista, porque você pode substituir os tipos de self da seguinte maneira:
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
Embora, se você substituir um membro de um tipo próprio, perderá o acesso ao membro original, que ainda poderá ser acessado por meio do super uso da herança. Então, o que realmente é ganho com o uso de herança é:
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
Agora, não posso pretender entender todas as sutilezas do padrão de bolo, mas me parece que o principal método de impor a modularidade é através da composição, e não da herança ou tipos próprios.
A versão da herança é mais curta, mas a principal razão pela qual prefiro a herança sobre os tipos próprios é que acho muito mais complicado obter a ordem de inicialização correta com os tipos próprios. No entanto, existem algumas coisas que você pode fazer com tipos próprios que você não pode fazer com herança. Auto-tipos podem usar um tipo, enquanto a herança exige uma característica ou uma classe, como em:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Você pode até fazer:
trait TypeBuster
{ this: Int with String => }
Embora você nunca seja capaz de instanciar isso. Não vejo nenhuma razão absoluta para não ser capaz de herdar de um tipo, mas certamente acho que seria útil ter classes e características de construtor de caminho, como temos características / classes de construtor de tipo. Infelizmente
trait InnerA extends Outer#Inner //Doesn't compile
Nós temos isso:
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 }
Ou isto:
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 }
Um ponto que deve ser mais enfatizado é que os traços podem estender as classes. Agradecemos a David Maclver por apontar isso. Aqui está um exemplo do meu próprio código:
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
herda da classe Swing Frame, para que possa ser usada como um tipo próprio e depois misturada no final (na instanciação). No entanto, val geomR
precisa ser inicializado antes de ser usado pela herança de características. Portanto, precisamos de uma classe para impor a inicialização prévia de geomR
. A classe ScnVista
pode ser herdada de várias características ortogonais das quais elas próprias podem ser herdadas. O uso de vários parâmetros de tipo (genéricos) oferece uma forma alternativa de modularidade.