Quelqu'un pourrait-il expliquer quand je voudrais utiliser Groovy Traits vs Mixins (@Mixin) vs Delegates (@Delegate)? Peut-être que certains compromis et problèmes de conception seraient utiles.
Ils semblent tous permettre de réutiliser plusieurs "classes" de comportement. Merci. :-)
Ce fil SO était également utile: Différence entre @Delegate et @Mixin AST transformations dans Groovy
Je suis d'accord, ils semblent tous permettre de réutiliser plusieurs "classes" de comportement. Il existe cependant des différences, et leur compréhension vous aidera probablement à prendre votre décision.
Avant de fournir un bref résumé/point culminant de chaque fonctionnalité et des exemples d'utilisation appropriée, résumons simplement la conclusion de chacune.
Conclusion/utilisation typique:
Et maintenant, examinons chacun d'eux avec un peu plus de détails.
@ Délégué
L'héritage est sur-utilisé dans de nombreux cas. Autrement dit, il est souvent mal utilisé. Les exemples classiques de Java étendent les flux d'entrée, les lecteurs ou les classes de collection. Pour la plupart d'entre eux, l'utilisation de l'héritage est trop étroitement associée à l'implémentation. Autrement dit, l'implémentation réelle est écrite de telle sorte que l'une des méthodes publiques en utilise une autre. Si vous remplacez les deux et que vous appelez super
, vous risquez d'obtenir des effets secondaires indésirables. Si l'implémentation change dans une version ultérieure, vous devrez mettre à jour votre gestion de celui-ci aussi.
Au lieu de cela, vous devez vous efforcer d'utiliser composition sur héritage .
Exemple, une liste de comptage qui compte les éléments ajoutés à une liste:
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
}
Dans cet exemple, le @Delegate
Supprime tout le code fastidieux de la plaque de chaudière pour toutes les méthodes publiques que vous souhaitez laisser "telles quelles", c'est-à-dire que des méthodes sont ajoutées qui transfèrent simplement l'appel à la liste sous-jacente. De plus, le CountingList
est séparé de l'implémentation afin que vous n'ayez pas à vous soucier de savoir si l'une de ces méthodes est implémentée en appelant l'autre. Dans l'exemple ci-dessus, c'est en fait le cas, puisque LinkedList.add(Collection)
appelle LinkedList.add(int, Collection)
, il ne serait donc pas aussi simple d'implémenter en utilisant l'héritage.
Sommaire:
@Delegate
À une classe. CountingList
dans l'exemple ci-dessus) ne sont pas des instances de la classe déléguée. CountingList
n'est pas une instance de LinkedList
.@ Mixin
La transformation @Mixin
Sera déconseillée avec groovy 2.3, en raison de la prise en charge des traits à venir. Cela donne une indication que tout ce qui est possible de faire avec @Mixin
, Devrait être possible avec des traits à la place.
D'après mon expérience, @Mixin
Est une sorte de bénédiction mitigée. :)
Il s'agit, selon l'admission des développeurs principaux, de bogues contenant des bogues "difficiles à résoudre". Cela ne veut pas dire que cela a été "inutile", loin de là. Mais si vous avez la possibilité d'utiliser (ou d'attendre) groovy 2.3, vous devriez plutôt utiliser des traits.
Ce que fait la transformation AST, c'est simplement d'ajouter les méthodes d'une classe dans une autre. Par exemple:
class First {
String hello(String name) { "Hello $name!" }
}
@Mixin(First)
class Second {
// more methods
}
assert new Second().hello('Vahid') == 'Hello Vahid!'
Sommaire:
Second
n'est pas une instance de First
Mixin d'exécution
Les mixins d'exécution et la transformation @Mixin
Sont très différents, ils résolvent différents cas d'utilisation et sont utilisés dans des situations totalement différentes. Puisqu'ils ont le même nom, il est facile de confondre l'un avec l'autre, ou de penser qu'ils sont une seule et même personne. Les mixins d'exécution, cependant, sont pas déconseillés dans groovy 2.3.
J'ai tendance à penser aux mixins d'exécution comme moyen d'ajouter des méthodes aux classes existantes, telles que n'importe quelle classe du JDK. C'est le mécanisme utilisé par Groovy pour ajouter des méthodes supplémentaires au JDK.
Exemple:
class MyStringExtension {
public static String hello(String self) {
return "Hello $self!"
}
}
String.mixin(MyStringExtension)
assert "Vahid".hello() == 'Hello Vahid!'
Groovy a également une fonctionnalité Nice module d'extension , où vous n'avez pas besoin d'effectuer manuellement le mixage, à la place, groovy le fait pour vous tant qu'il trouve le descripteur de module à l'emplacement correct dans le chemin de classe.
Sommaire:
Traits
Les traits sont nouveaux dans groovy 2.3.
J'ai tendance à voir ces traits comme quelque chose entre l'interface familière et la classe. Quelque chose qui ressemble à une classe "légère". Ils sont appelés "interfaces avec implémentations et état par défaut" dans la documentation.
Les traits sont similaires à la transformation @Mixin
Qu'ils remplacent, mais ils sont également plus puissants. Pour commencer, ils sont beaucoup plus bien définis. Un trait ne peut pas être instancié directement, tout comme une interface, ils ont besoin d'une classe d'implémentation. Et une classe peut implémenter de nombreux traits.
Un exemple simple:
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 différence immédiate entre les traits et @Mixin est que trait
est un mot-clé de langage, pas une transformation AST. De plus, il peut contenir des méthodes abstraites qui doivent être implémentées par la classe De plus, une classe peut implémenter plusieurs traits. La classe implémentant un trait est une instance de ce trait.
Sommaire: