Comencemos con la dependencia cíclica.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Sin embargo, la modularidad de esta solución no es tan buena como podría parecer en un principio, ya que puede anular los tipos propios de esta manera:
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
Sin embargo, si anula un miembro de tipo automático, pierde el acceso al miembro original, al que aún se puede acceder a través de super usando la herencia. Entonces, lo que realmente se gana con el uso de la herencia es:
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
Ahora no puedo afirmar que entiendo todas las sutilezas del patrón de pastel, pero me sorprende que el método principal para imponer la modularidad es a través de la composición en lugar de la herencia o los tipos propios.
La versión de herencia es más corta, pero la razón principal por la que prefiero la herencia sobre los tipos propios es que me resulta mucho más difícil obtener el orden de inicialización correcto con los tipos propios. Sin embargo, hay algunas cosas que puede hacer con los tipos propios que no puede hacer con la herencia. Los tipos propios pueden usar un tipo mientras que la herencia requiere un rasgo o una clase como en:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Incluso puedes hacer:
trait TypeBuster
{ this: Int with String => }
Aunque nunca podrás instanciarlo. No veo ninguna razón absoluta para no poder heredar de un tipo, pero ciertamente creo que sería útil tener clases y rasgos de constructor de ruta ya que tenemos rasgos / clases de constructor de tipo. Como lamentablemente
trait InnerA extends Outer#Inner //Doesn't compile
Tenemos esto:
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 }
O esto:
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 }
Un punto que debería empatizarse más es que los rasgos pueden extender las clases. Gracias a David Maclver por señalar esto. Aquí hay un ejemplo de mi propio 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
hereda de la clase Swing Frame, por lo que podría usarse como un tipo automático y luego mezclarse al final (en la instanciación). Sin embargo, val geomR
debe inicializarse antes de que se use heredando rasgos. Por lo tanto, necesitamos una clase para imponer la inicialización previa de geomR
. La clase ScnVista
puede ser heredada de múltiples rasgos ortogonales de los cuales ellos mismos pueden ser heredados. El uso de múltiples parámetros de tipo (genéricos) ofrece una forma alternativa de modularidad.