¿Cuál es la ventaja de usar clases abstractas en lugar de rasgos?


¿Cuál es la ventaja de usar una clase abstracta en lugar de un rasgo (aparte del rendimiento)? Parece que las clases abstractas pueden ser reemplazadas por rasgos en la mayoría de los casos.

 330
Author: Jacek Laskowski, 2010-01-02

8 answers

Se me ocurren dos diferencias

  1. Las clases abstractas pueden tener parámetros de constructor así como parámetros de tipo. Los rasgos solo pueden tener parámetros de tipo. Hubo cierta discusión de que en el futuro incluso los rasgos pueden tener parámetros de constructor
  2. Las clases abstractas son completamente interoperables con Java. Puede llamarlos desde código Java sin ningún envoltorio. Los rasgos son completamente interoperables solo si no contienen ningún código de implementación
 330
Author: Mushtaq Ahmed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-02 11:53:57

Hay una sección en Programación en Scala llamada "To trait, or not to trait?" que aborda esta cuestión. Dado que el 1er ed está disponible en línea, espero que esté bien citar todo el asunto aquí. (Cualquier programador serio de Scala debe comprar el libro):

Cada vez que implemente una colección reutilizable de comportamiento, tienes que decidir si quieres usar un rasgo o una clase abstracta. No hay una regla firme, pero esta sección contiene algunas pautas para considerar.

Si el comportamiento no será reutilizado , entonces hazlo una clase concreta. Se no es un comportamiento reutilizable después de todo.

Si se puede reutilizar en múltiples clases no relacionadas, conviértalo en un rasgo. Solo los rasgos pueden mezclarse en diferentes partes de la jerarquía de clases.

Si desea heredar de él en código Java, utilice una clase abstracta. Dado que los rasgos con código no tienen un análogo Java cercano, tiende a ser torpe a heredar de un rasgo en una clase Java. Heredar de un La clase Scala, por su parte, es exactamente como heredar de una clase Java. Como una excepción, un rasgo de Scala con solo miembros abstractos se traduce directamente a una interfaz Java, por lo que debe sentirse libre de definir tal rasgos incluso si espera que el código Java herede de él. Véase 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 fuera grupos para escribir clases heredadas de él, puede inclinarse hacia usando una clase abstracta. El problema es que cuando un rasgo gana o pierde un miembro, cualquier clase que herede de él debe ser recompilada, incluso si no han cambiado. Si los clientes externos sólo llamarán a la 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 Java los tiempos de ejecución crean un método virtual invocación de un miembro de la clase a más rápido operación que una invocación de método de interfaz. Los rasgos se compilan para interfaces y por lo tanto puede pagar 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 tienen pruebas que usar una clase en su lugar realmente resuelve el problema.

Si todavía no sabe, después de considerar lo anterior, comience por haciéndolo como un rasgo. Usted siempre se puede cambiar más tarde, y en general el uso de 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 clases, super las llamadas están enlazadas estáticamente, en rasgos, están enlazadas dinámicamente. Si escribes super.toString en una clase, sabes exactamente cuál se invocará la implementación del método. Sin embargo, cuando se escribe lo mismo en un trait, la implementación del método a invocar para la super llamada no está definida cuando se define el trait.

Ver el resto de Capítulo 12 para más detalles.

Editar 1 (2013):

Hay una diferencia sutil 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 preserva la jerarquía de herencia de las clases, que tiende para impulsar clases abstractas más adelante en la cadena, mientras que los rasgos pueden mezclarse felizmente. En ciertas circunstancias, es realmente preferible estar en la última posición de la linealización de clases, por lo que las clases abstractas podrían usarse para eso. Ver restringir la linealización de clases (en orden mixto) en Scala.

Editar 2 (2018):

A partir de Scala 2.12, el comportamiento de compatibilidad binaria de trait ha cambiado. Antes de la versión 2.12, agregar o eliminar un miembro al rasgo requería recompilación de todas las clases que heredan el rasgo, incluso si las clases no han cambiado. Esto se debe a la forma en que los rasgos fueron codificados en JVM.

A partir de Scala 2.12, traits compila para interfaces Java, por lo que el requisito se ha relajado un poco. Si el rasgo hace alguna de las siguientes cosas, sus subclases aún requieren recompilación:

  • definiendo campos (val o var, pero una constante es ok - final val sin tipo de resultado)
  • llamando a super
  • inicializador declaraciones 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.

 177
Author: Eugene Yokota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-21 20:40:04

Para cualquier cosa que valga la pena, la programación de Odersky et al en Scala recomienda que, cuando dude, use rasgos. Siempre puede cambiarlos a clases abstractas más adelante si es necesario.

 72
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-02 14:45:44

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 enlazadas dinámicamente (se refiere a una clase o rasgo mezclado antes de la 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
 17
Author: Nemanja Boric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:03:02

Cuando se extiende una clase abstracta, esto muestra que la subclase es de un tipo similar. Este no es necesariamente el caso cuando se usan rasgos, creo.

 9
Author: peter p,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-02 09:23:36

En Programando Scala los autores dicen que las clases abstractas hacen una relación clásica orientada a objetos "is-a", mientras que los rasgos son una forma de composición de scala.

 7
Author: CQQL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-23 10:22:34

Las clases abstractas pueden contener behaviour - Pueden parametrizarse con args de constructor (que los rasgos no pueden) y representar una entidad de trabajo. En cambio, los rasgos solo representan una sola característica, una interfaz de una funcionalidad.

 5
Author: Dario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-02 09:34:43
  1. Una clase puede heredar de múltiples rasgos pero solo de una clase abstracta.
  2. Las clases abstractas pueden tener parámetros de constructor así como parámetros de tipo. Los rasgos solo pueden tener parámetros de tipo. Por ejemplo, no se puede decir trait t (i: Int) {}; el parámetro i es ilegal.
  3. Las clases abstractas son completamente interoperables con Java. Puede llamarlos desde código Java sin ningún envoltorio. Los rasgos son completamente interoperables solo si no contienen ningún código de implementación.
 1
Author: pavan.vn101,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-23 20:52:26