Cuando buscas en Google, surgen muchas respuestas para este tema. Sin embargo, no creo que ninguno de ellos haga un buen trabajo al ilustrar la diferencia entre estas dos características. Así que me gustaría probar una vez más, específicamente ...
¿Qué es algo que se puede hacer con auto-tipos y no con herencia, y viceversa?
Para mí, debería haber alguna diferencia física cuantificable entre los dos, de lo contrario son nominalmente diferentes.
Si el rasgo A extiende a B o al tipo B, ¿no ilustran ambos que ser B es un requisito? ¿Dónde está la diferencia?
design-patterns
object-oriented-design
scala
Mark Canlas
fuente
fuente
Respuestas:
Si el rasgo A extiende a B, entonces mezclar en A le da exactamente B más lo que A agrega o extiende. Por el contrario, si el rasgo A tiene una autorreferencia que se escribe explícitamente como B, entonces la clase padre última también debe mezclar B o un tipo descendiente de B (y mezclarlo primero , lo cual es importante).
Esa es la diferencia más importante. En el primer caso, el tipo preciso de B se cristaliza en el punto A lo extiende. En el segundo, el diseñador de la clase principal decide qué versión de B se utiliza, en el punto donde se compone la clase principal.
Otra diferencia es donde A y B proporcionan métodos del mismo nombre. Donde A extiende a B, el método de A anula a B. Cuando A se mezcla después de B, el método de A simplemente gana.
La autorreferencia mecanografiada te da mucha más libertad; El acoplamiento entre A y B está suelto.
ACTUALIZAR:
Como no tiene claro el beneficio de estas diferencias ...
Si usa la herencia directa, entonces crea el rasgo A que es B + A. Has establecido la relación en piedra.
Si usa una autorreferencia escrita, cualquiera que quiera usar su rasgo A en la clase C podría
Y este no es el límite de sus opciones, dada la forma en que Scala le permite instanciar un rasgo directamente con un bloque de código como su constructor.
En cuanto a la diferencia entre el método ganador de A , porque A se mezcla en último lugar, en comparación con A que extiende B, considere esto ...
Cuando se mezcla una secuencia de rasgos, cada vez que
foo()
se invoca el método , el compilador va al último rasgo mezclado para buscarfoo()
, luego (si no se encuentra), atraviesa la secuencia hacia la izquierda hasta que encuentra un rasgo que implementafoo()
y utiliza ese. A también tiene la opción de llamarsuper.foo()
, que también atraviesa la secuencia hacia la izquierda hasta que encuentra una implementación, y así sucesivamente.Entonces, si A tiene una autorreferencia escrita a B y el escritor de A sabe que B implementa
foo()
, A puede llamarsuper.foo()
sabiendo que si nada más proporcionafoo()
, B lo hará. Sin embargo, el creador de la clase C tiene la opción de eliminar cualquier otro rasgo en el que se implementefoo()
, y A lo obtendrá en su lugar.De nuevo, esto es mucho más potente y menos limitante que A que se extiende B y llamando directamente a la versión de B de
foo()
.fuente
Tengo un código que ilustra algunas de las diferencias en la visibilidad de wrt y las implementaciones "predeterminadas" cuando se extiende frente a la configuración de un auto-tipo. No ilustra ninguna de las partes discutidas ya sobre cómo se resuelven las colisiones de nombres reales, sino que se centra en lo que es posible y no es posible hacer.
Una diferencia importante de la OMI es que
A1
no expone que es necesarioB
para nada que simplemente lo veaA1
(como se ilustra en las partes mejoradas). El único código que realmente verá queB
se utiliza una especialización particular es el código que sabe explícitamente sobre el tipo compuesto (comoThink*{X,Y}
).Otro punto es que
A2
(con extensión) realmente se usaráB
si no se especifica nada más, mientras queA1
(auto-tipo) no dice que se usará aB
menos que se anule, se debe dar explícitamente una B concreta cuando se instancian los objetos.fuente