trait pattern functions examples example companion classes basics scala composition mixins

pattern - scala trait



Mixins vs composiciĆ³n en scala (2)

En el mundo java (más precisamente si no tienes herencia múltiple / mixins) la regla de oro es bastante simple: "Favorece la composición de objetos sobre la herencia de clase".

Me gustaría saber si / cómo se cambia si también se consideran mixinas, especialmente en scala.
¿Los mixins se consideran una forma de herencia múltiple o más composición de clase?
¿Existe también una pauta "Favorecer la composición del objeto sobre la composición de la clase" (o al revés)?

He visto algunos ejemplos cuando las personas usan (o abusan) mixins cuando la composición de objetos también puede hacer el trabajo y no siempre estoy seguro de cuál es mejor. Me parece que puedes lograr cosas bastante similares con ellos, pero también hay algunas diferencias, algunos ejemplos:

  • visibilidad: con mixins todo se convierte en parte de la API pública, que no es el caso con la composición.
  • verbosidad: en la mayoría de los casos, los mixins son menos detallados y un poco más fáciles de usar, pero no siempre es así (por ejemplo, si también usa self-types en jerarquías complejas)

Sé que la respuesta corta es "Depende", pero probablemente haya alguna situación típica cuando esto o aquello sea mejor.

Algunos ejemplos de pautas que podría presentar hasta ahora (suponiendo que tengo dos rasgos A y B y A quiere usar algunos métodos de B):

  • Si desea extender la API de A con los métodos de B, mixins, de lo contrario, composición. Pero no ayuda si la clase / instancia que estoy creando no es parte de una API pública.
  • Si desea utilizar algunos patrones que necesitan mixins (por ejemplo, Stackable Trait Pattern ), entonces es una decisión fácil.
  • Si tiene dependencias circulares, las mezclas con tipos propios pueden ayudar. (Intento evitar esta situación, pero no siempre es fácil)
  • Si desea algunas decisiones dinámicas en tiempo de ejecución, cómo hacer la composición y luego la composición del objeto.

En muchos casos, mixins parece ser más fácil (y / o menos detallado), pero estoy bastante seguro de que también tienen algunas trampas, como la "clase de Dios" y otras descritas en dos artículos artima: parte 1 , parte 2 (por cierto me parece que la mayoría de los otros problemas no son relevantes / no tan graves para scala).

¿Tienes más pistas como estas?


Muchos de los problemas que las personas tienen con las mezclas pueden evitarse en Scala si solo se combinan los rasgos abstractos en las definiciones de clase y luego se mezclan los rasgos concretos correspondientes en el momento de la instanciación del objeto. Por ejemplo

trait Locking{ // abstract locking trait, many possible definitions protected def lock(body: =>A):A } class MyService{ this:Locking => } //For this time, we''ll use a java.util.concurrent lock val myService:MyService = new MyService with JDK15Locking

Esta construcción tiene varias cosas para recomendar. Primero, evita que haya una explosión de clases ya que se necesitan diferentes combinaciones de funcionalidades de rasgo. En segundo lugar, permite realizar pruebas fácilmente, ya que uno puede crear y mezclar rasgos concretos de "no hacer nada", similares a los objetos simulados. Finalmente, hemos ocultado por completo el rasgo de bloqueo utilizado, e incluso ese bloqueo, de los consumidores de nuestro servicio.

Como hemos superado la mayoría de los inconvenientes de las mezclas, aún nos queda una compensación entre la mezcla y la composición. Para mí, normalmente tomo la decisión en función de si un objeto delegado hipotético estaría completamente encapsulado por el objeto contenedor, o si podría compartirse potencialmente y tener un ciclo de vida propio. Locking proporciona un buen ejemplo de delegados completamente encapsulados. Si su clase usa un objeto de bloqueo para administrar el acceso simultáneo a su estado interno, ese bloqueo está completamente controlado por el objeto que lo contiene, y ni él ni sus operaciones se anuncian como parte de la interfaz pública de la clase. Para funcionalidad completamente encapsulada como esta, voy con mezclas. Para algo compartido, como un origen de datos, use composición.


Otras diferencias que no has mencionado:

  • Las clases de rasgos no tienen existencia independiente:

( Programación de Scala )

Si encuentra que un rasgo en particular se usa con más frecuencia como padre de otras clases, para que las clases secundarias se comporten como el rasgo principal, entonces considere definir el rasgo como una clase en su lugar, para hacer que esta relación lógica sea más clara.
(Dijimos que se comporta como , en lugar de ser , porque la primera es la definición más precisa de herencia, basada en el Principio de sustitución de Liskov - ver [Martin2003], por ejemplo).

[Martin2003]: Robert C. Martin, Desarrollo ágil de software: Principios, patrones y prácticas, Prentice-Hall, 2003

  • mixins ( trait ) no tienen parámetros de constructor.

De ahí el consejo, aún de Programming Scala :

Evite campos concretos en rasgos que no se puedan inicializar a valores predeterminados adecuados.
Use campos abstractos en su lugar o convierta el rasgo a una clase con un constructor .
Por supuesto, los rasgos sin estado no tienen ningún problema con la inicialización.

Es un principio general de buen diseño orientado a objetos que una instancia siempre debe estar en un estado válido conocido, comenzando desde el momento en que finaliza el proceso de construcción .

Esa última parte, con respecto al estado inicial de un objeto, a menudo ha ayudado a decidir entre la clase (y la composición de clase) y el rasgo (y mixins) para un concepto dado.