¿Cuándo usar rasgos, en lugar de herencia y composición?

9

Hay tres formas comunes, AFAIK, de implementar la reutilización cuando se trata de OOP

  1. Herencia: generalmente para representar es una relación (un pato es un pájaro)
  2. Composición: generalmente para representar una relación tiene (un automóvil tiene un motor)
  3. Rasgos (por ejemplo, la palabra clave rasgo en PHP): ... no estoy muy seguro de esto

Si bien me parece que los rasgos pueden implementar relaciones has-a y is-a, no estoy realmente seguro de a qué tipo de modelado se destina. ¿Para qué tipo de situaciones se diseñaron los rasgos?

Extrakun
fuente
Puede explicar un poco más sobre los rasgos, pero ¿podrían los rasgos ser otra palabra para las composiciones?
Trilarion
1
Realmente me gustaría saber por qué también. No los he usado una vez.
Andy

Respuestas:

6

Los rasgos son otra forma de hacer composición. Piense en ellos como una forma de componer todas las partes de una clase en tiempo de compilación (o tiempo de compilación JIT), ensamblando las implementaciones concretas de las partes que necesitará.

Básicamente, quieres usar rasgos cuando te encuentres haciendo clases con diferentes combinaciones de características. Esta situación se presenta con mayor frecuencia para las personas que escriben bibliotecas flexibles para que otras las consuman. Por ejemplo, aquí está la declaración de una clase de prueba unitaria que escribí recientemente usando ScalaTest :

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

Los marcos de prueba unitaria tienen un montón de opciones de configuración diferentes, y cada equipo tiene diferentes preferencias sobre cómo quieren hacer las cosas. Al poner las opciones en rasgos (que se mezclan al usar withen Scala), ScalaTest puede ofrecer todas esas opciones sin tener que crear nombres de clase como WordSpecLikeWithMatchersAndFutures, o un montón de banderas booleanas en tiempo de ejecución como WordSpecLike(enableFutures, enableMatchers, ...). Esto facilita seguir el principio Abierto / cerrado . Puede agregar nuevas características y nuevas combinaciones de características, simplemente agregando un nuevo rasgo. También hace que sea más fácil seguir el Principio de segregación de interfaz , ya que puede colocar fácilmente funciones que no son universalmente necesarias en un rasgo.

Los rasgos también son una buena forma de poner código común en varias clases que no tienen sentido compartir una jerarquía de herencia. La herencia es una relación muy estrecha, y no debe pagar ese costo si puede evitarlo. Los rasgos son una relación mucho más débilmente acoplada. En mi ejemplo anterior, solía MyCustomTraitcompartir fácilmente una implementación de base de datos simulada entre varias clases de prueba que de otro modo no estarían relacionadas.

La inyección de dependencia logra muchos de los mismos objetivos, pero en tiempo de ejecución basado en la entrada del usuario en lugar de en tiempo de compilación basado en la entrada del programador. Los rasgos también están destinados más a dependencias que son semánticamente parte de la misma clase. Es como reunir las partes de una clase en lugar de hacer llamadas a otras clases con otras responsabilidades.

Los marcos de inyección de dependencia logran muchos de los mismos objetivos en tiempo de compilación basados ​​en la entrada del programador, pero son en gran medida una solución alternativa para los lenguajes de programación sin el soporte de rasgos adecuado. Los rasgos traen estas dependencias al dominio del verificador de tipo del compilador, con una sintaxis más limpia, con un proceso de compilación más simple, que hace una distinción más clara entre dependencias de tiempo de compilación y tiempo de ejecución.

Karl Bielefeldt
fuente
Hola, esta es una gran respuesta. ¿Puedo preguntar cómo decidir cuándo usar los rasgos y cuándo usar la inyección de dependencia? que parece ser lo mismo.
Extrakun
Observación astuta. Mira mi edición.
Karl Bielefeldt
Gracias por escribir esto. Tengo curiosidad por su respuesta sobre los rasgos que son como la composición @KarlBielefeldt. Su clase se puede usar donde sea que se necesite ese rasgo, y aún sufriría la misma fragilidad que con una interfaz normal. Entonces parece más cerca del subtipo y la herencia, ¿no? Tampoco estoy seguro de cómo es similar a DI ... si tiene varias clases definidas que extienden rasgos, entonces esas dependencias concretas no están definidas en cualquier lugar de la jerarquía en que se define esa clase, en lugar de en un punto superior / de entrada del ¿código? ¡Gracias!
Allstar
Los rasgos se pueden usar como las interfaces, por sus propiedades de herencia, pero eso no es lo que los hace únicos. El withoperador es lo que los diferencia, y withes una operación de composición. Y sí, los rasgos reemplazan DI sin necesidad de definirse en el punto de entrada del código. Esa es una de las cosas que los hace preferibles en mi opinión.
Karl Bielefeldt