design patterns - ¿Diferencia entre @Delegate, @Mixin y Traits en Groovy?
design-patterns delegates (1)
Estoy de acuerdo, todos parecen permitir la reutilización de múltiples "clases" de comportamiento. Sin embargo, existen diferencias, y entender esto probablemente ayudará a su decisión.
Antes de proporcionar un breve resumen / resaltado de cada característica y ejemplos de uso adecuado, resumamos la conclusión de cada uno.
Conclusión / uso típico:
- @Delegate : se usa para agregar toda la funcionalidad de la clase de delegado, pero aún así evitar el acoplamiento estricto a la implementación real. Vamos a lograr la composición sobre la herencia .
- @Mixin : obsoleto con groovy 2.3. Forma simple de agregar métodos de una o más clases a tu clase. Bug-montado.
- Mixin en tiempo de ejecución : agregue uno o más métodos en cualquier clase existente, por ejemplo, una clase en el JDK o una biblioteca de terceros.
- Rasgos : nuevo en groovy 2.3. Una forma bien definida de agregar uno o más rasgos a tu clase. Reemplaza a @Mixin. El único de estos donde los métodos agregados son visibles en las clases de Java.
Y ahora, veamos cada uno de estos con un poco más de detalle.
@Delegar
La herencia se usa en exceso en muchos casos. Es decir, a menudo se usa incorrectamente. Los ejemplos clásicos en Java están ampliando los flujos de entrada, los lectores o las clases de colección. Para la mayoría de estos, el uso de la herencia está muy unido a la implementación. Es decir, la implementación real está escrita para que uno de los métodos públicos realmente use otro. Si anulas ambos y llamas a super
, es posible que tengas efectos secundarios no deseados. Si la implementación cambia en una versión posterior, también deberá actualizar su manejo de la misma.
En cambio, debe esforzarse por usar la composición sobre la herencia .
Ejemplo, una lista de conteo que cuenta los elementos agregados a una lista:
class CountingList<E> {
int counter = 0
@Delegate LinkedList<E> list = new LinkedList<>()
boolean addAll(Collection<? extends E> c) {
counter += c.size()
list.addAll(c)
}
boolean addAll(int index, Collection<? extends E> c) {
counter += c.size()
list.addAll(index, c)
}
// more add methods with counter updates
}
En este ejemplo, @Delegate
elimina todo el tedioso código de placa de caldera para todos los métodos públicos que desea dejar "tal como está", es decir, se agregan métodos que simplemente reenvía la llamada a la lista subyacente. Además, CountingList
se separa de la implementación para que no tenga que importar si uno de estos métodos se implementa llamando al otro. En el ejemplo anterior, ese es realmente el caso, ya que LinkedList.add(Collection)
llama a LinkedList.add(int, Collection)
, por lo que no sería tan fácil de implementar usando herencia.
Resumen:
- Proporciona implementaciones predeterminadas para todos los métodos públicos en el objeto delegado.
- Los métodos con la misma firma que se agregan explícitamente tienen prioridad.
- Los métodos agregados implícitamente no son visibles en Java.
- Puede agregar varios
@Delegate
s a una clase. - La clase con delegados (
CountingList
en el ejemplo anterior) no son instancias de la clase delegada.- Es decir,
CountingList
no es una instancia deLinkedList
.
- Es decir,
- Se usa para evitar un acoplamiento fuerte a través de la herencia.
@Mixin
La transformación @Mixin
quedará obsoleta con Groovy 2.3, debido al soporte de los rasgos próximos. Esto proporciona una pista de que todo lo que es posible hacer con @Mixin
, debería ser posible con los rasgos.
En mi experiencia, @Mixin
es una especie de bendición mixta. :)
Es, según la admisión de los desarrolladores centrales, plagado de errores "difíciles de resolver". Eso no quiere decir que haya sido "inútil", ni mucho menos. Pero si tienes la oportunidad de usar (o esperar) Groovy 2.3, entonces deberías usar rasgos en su lugar.
Lo que hace la transformación AST es simplemente agregar los métodos de una clase a otra. Por ejemplo:
class First {
String hello(String name) { "Hello $name!" }
}
@Mixin(First)
class Second {
// more methods
}
assert new Second().hello(''Vahid'') == ''Hello Vahid!''
Resumen:
- Agrega métodos de una clase a otra.
- Usar en groovy <2.3 para la simple adición de métodos de una clase a otra
- no agregue a las clases "súper" (al menos, he tenido problemas con eso)
- Bug-ridden
- Obsoleto de Groovy 2.3
- Los métodos agregados implícitamente no son visibles en Java.
- La clase en la que se mezcla otra clase, no son instancias de esa otra clase
- Es decir, el
Second
no es una instancia deFirst
- Es decir, el
- Puedes mezclar varias clases en otra clase
- Usar como un método simple para agregar la funcionalidad de una clase a otra en groovy <2.3
Mixin en tiempo de ejecución
Los mixins en tiempo de ejecución y la transformada @Mixin
son bastante diferentes, resuelven diferentes casos de uso y se usan en situaciones totalmente diferentes. Como tienen el mismo nombre, es fácil confundir uno con el otro, o pensar que son uno y el mismo. Mixins en tiempo de ejecución, sin embargo, no están en desuso en groovy 2.3.
Tiendo a pensar en mixins en tiempo de ejecución como la forma de agregar métodos a las clases existentes, como cualquier clase en el JDK. Es el mecanismo utilizado por Groovy para agregar métodos extra al JDK.
Ejemplo:
class MyStringExtension {
public static String hello(String self) {
return "Hello $self!"
}
}
String.mixin(MyStringExtension)
assert "Vahid".hello() == ''Hello Vahid!''
Groovy también tiene una función de módulo de extensión agradable, donde no es necesario realizar manualmente la mezcla, sino que Groovy lo hace por usted siempre que encuentre el descriptor del módulo en la ubicación correcta en el classpath.
Resumen:
- Agregar métodos a cualquier clase existente
- cualquier clase en el JDK
- cualquier clase de terceros
- o cualquiera de tus propias clases
- Sobrescribe cualquier método existente con la misma firma
- Los métodos agregados no son visibles en Java
- Normalmente se usa para extender las clases existentes / de terceros con nuevas funcionalidades
Rasgos
Los rasgos son nuevos en groovy 2.3.
Tiendo a ver estos rasgos como algo entre la interfaz familiar y la clase. Algo parecido a una clase "ligera". Se denominan "interfaces con implementaciones y estado predeterminados" en la documentación.
Los rasgos son similares a la transformación @Mixin
que reemplazan, pero también son más potentes. Para empezar, están mucho mejor definidos. Un rasgo no se puede instanciar directamente, al igual que una interfaz, necesitan una clase de implementación. Y una clase puede implementar muchos rasgos.
Un simple ejemplo:
trait Name {
abstract String name()
String myNameIs() { "My name is ${name()}!" }
}
trait Age {
int age() { 42 }
}
class Person implements Name, Age {
String name() { ''Vahid'' }
}
def p = new Person()
assert p.myNameIs() == ''My name is Vahid!''
assert p.age() == 42
assert p instanceof Name
assert p instanceof Age
La diferencia inmediata entre los rasgos y @Mixin es que el trait
es una palabra clave del lenguaje, no una transformación AST. Además, puede contener métodos abstractos que la clase debe implementar. Además, una clase puede implementar varios rasgos. La clase que implementa un rasgo es una instancia de ese rasgo.
Resumen:
- Los rasgos proporcionan una interfaz con la implementación y el estado.
- Una clase puede implementar múltiples rasgos.
- Los métodos implementados por un rasgo son visibles en Java.
- Compatible con comprobación de tipos y compilación estática.
- Los rasgos pueden implementar interfaces.
- Los rasgos no pueden ser instanciados por ellos mismos.
- Un rasgo puede extender otro rasgo.
- El manejo del problema de los diamantes está bien definido.
- Uso típico:
- agrega rasgos similares a diferentes clases.
- (como alternativa al AOP)
- componer una nueva clase de varios rasgos.
- agrega rasgos similares a diferentes clases.
¿Alguien me explicaría cuándo me gustaría usar Groovy Traits vs. Mixins (@Mixin) vs. Delegados (@Delegate)? Tal vez algunas concesiones y preocupaciones de diseño ayudarían.
Todos parecen permitir la reutilización de múltiples "clases" de comportamiento. Gracias. :-)
Este hilo SO también fue útil: Diferencia entre las transformaciones @Delegate y @Mixin AST en Groovy