Respuestas:
Se me ocurren dos diferencias
Hay una sección en Programación en Scala llamada "¿Rasgo o no rasgo?" que aborda esta pregunta. Como la primera edición está disponible en línea, espero que esté bien citar todo aquí. (Cualquier programador serio de Scala debería comprar el libro):
Cada vez que implemente una colección reutilizable de comportamiento, tendrá que decidir si desea usar un rasgo o una clase abstracta. No existe una regla firme, pero esta sección contiene algunas pautas a tener en cuenta.
Si el comportamiento no se reutilizará , conviértalo en una clase concreta. No es un comportamiento reutilizable después de todo.
Si se puede reutilizar en varias clases no relacionadas , conviértalo en un rasgo. Solo los rasgos se pueden mezclar en diferentes partes de la jerarquía de clases.
Si desea heredar de él en código Java , use una clase abstracta. Dado que los rasgos con código no tienen un análogo de Java cercano, tiende a ser incómodo heredar de un rasgo en una clase de Java. Heredar de una clase Scala, mientras tanto, es exactamente como heredar de una clase Java. Como una excepción, un rasgo Scala con solo miembros abstractos se traduce directamente a una interfaz Java, por lo que debe sentirse libre de definir dichos rasgos incluso si espera que el código Java herede de él. Consulte el Capítulo 29 para obtener más información sobre cómo trabajar con Java y Scala juntos.
Si planea distribuirlo en forma compilada y espera que grupos externos escriban clases heredadas de él, puede inclinarse hacia el uso de una clase abstracta. El problema es que cuando un rasgo gana o pierde un miembro, cualquier clase que herede de él debe volver a compilarse, incluso si no ha cambiado. Si los clientes externos solo recurrirán al comportamiento, en lugar de heredar de él, entonces usar un rasgo está bien.
Si la eficiencia es muy importante , inclínese hacia el uso de una clase. La mayoría de los tiempos de ejecución de Java hacen que la invocación de un método virtual de un miembro de la clase sea una operación más rápida que la invocación de un método de interfaz. Los rasgos se compilan en las interfaces y, por lo tanto, pueden generar una ligera sobrecarga de rendimiento. Sin embargo, debe hacer esta elección solo si sabe que el rasgo en cuestión constituye un cuello de botella de rendimiento y tiene evidencia de que el uso de una clase en realidad resuelve el problema.
Si aún no lo sabe , después de considerar lo anterior, comience haciéndolo como un rasgo. Siempre puede cambiarlo más tarde y, en general, usar un rasgo mantiene más opciones abiertas.
Como @Mushtaq Ahmed mencionó, un rasgo no puede tener ningún parámetro pasado al constructor primario de una clase.
Otra diferencia es el tratamiento de super
.
La otra diferencia entre clases y rasgos es que, mientras que en las clases, las
super
llamadas están vinculadas estáticamente, en los rasgos, están vinculadas dinámicamente. Si escribesuper.toString
en una clase, sabe exactamente qué implementación de método se invocará. Sin embargo, cuando escribe lo mismo en un rasgo, la implementación del método para invocar la súper llamada no está definida cuando define el rasgo.
Vea el resto del Capítulo 12 para más detalles.
Edición 1 (2013):
Hay una sutil diferencia en la forma en que las clases abstractas se comportan en comparación con los rasgos. Una de las reglas de linealización es que conserva la jerarquía de herencia de las clases, lo que tiende a impulsar las clases abstractas más adelante en la cadena, mientras que los rasgos pueden mezclarse felizmente. En ciertas circunstancias, es preferible estar en la última posición de la linealización de clases , por lo que podrían usarse clases abstractas para eso. Ver linealización de clase restrictiva (orden de mezcla) en Scala .
Edición 2 (2018):
A partir de Scala 2.12, el comportamiento de compatibilidad binaria del rasgo ha cambiado. Antes de 2.12, agregar o eliminar un miembro al rasgo requería la compilación de todas las clases que heredan el rasgo, incluso si las clases no han cambiado. Esto se debe a la forma en que se codificaron los rasgos en JVM.
A partir de Scala 2.12, los rasgos se compilan en interfaces Java , por lo que el requisito se ha relajado un poco. Si el rasgo hace algo de lo siguiente, sus subclases aún requieren recompilación:
- definir campos (
val
ovar
, pero una constante está bien,final val
sin tipo de resultado)- vocación
super
- declaraciones de inicializador en el cuerpo
- extendiendo una clase
- confiando en la linealización para encontrar implementaciones en el supertrait correcto
Pero si el rasgo no lo hace, ahora puede actualizarlo sin romper la compatibilidad binaria.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- ¿Alguien podría explicar cuál es la diferencia aquí? extends
vs with
?
extends
y with
. Es puramente sintáctico. Si hereda de varias plantillas, la primera obtiene extend
, todas las demás obtienen with
, eso es todo. Piense with
como una coma: class Foo extends Bar, Baz, Qux
.
Para lo que sea que valga la pena, la Programación en Scala de Odersky et al recomienda que, cuando dudes, uses rasgos. Siempre puede cambiarlos a clases abstractas más adelante si es necesario.
Aparte del hecho de que no puede extender directamente múltiples clases abstractas, pero puede mezclar múltiples rasgos en una clase, vale la pena mencionar que los rasgos son apilables, ya que las súper llamadas en un rasgo están vinculadas dinámicamente (se refiere a una clase o rasgo mezclado antes) el actual).
De la respuesta de Thomas en Diferencia entre clase abstracta y rasgo :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
En Programming Scala, los autores dicen que las clases abstractas hacen una relación clásica "es-una" orientada a objetos, mientras que los rasgos son una forma de composición de escala.
Las clases abstractas pueden contener comportamiento: pueden parametrizarse con argumentos de constructor (que los rasgos no pueden) y representan una entidad de trabajo. En cambio, los rasgos solo representan una característica única, una interfaz de una funcionalidad.
trait Enumerable
con muchas funciones auxiliares, no las llamaría comportamiento sino solo funcionalidad relacionada con una característica.