web-dev-qa-db-fra.com

Différence entre @Delegate, @Mixin et Traits in Groovy?

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

55
Vahid Pazirandeh

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:

  • @ Delegate : Utilisé pour ajouter toutes les fonctionnalités de la classe déléguée, mais toujours éviter de coupler étroitement à l'implémentation réelle. Laissez-vous atteindre composition sur héritage .
  • @ Mixin : obsolète avec groovy 2.3. Un moyen simple d'ajouter des méthodes d'une ou plusieurs classes dans votre classe. Insecte.
  • Mixin d'exécution : Ajoutez une ou plusieurs méthodes dans any classe existante, par ex. une classe dans le JDK ou une bibliothèque tierce.
  • Traits : Nouveau dans groovy 2.3. Manière bien définie d'ajouter un ou plusieurs traits à votre classe. Remplace @Mixin. La seule d'entre elles où les méthodes ajoutées sont visibles dans les classes Java.

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:

  • Fournit des implémentations par défaut pour toutes les méthodes publiques dans l'objet délégué.
    • Les méthodes avec la même signature qui sont explicitement ajoutées ont priorité.
  • Les méthodes implicitement ajoutées sont pas visibles en Java.
  • Vous pouvez ajouter plusieurs @Delegate À une classe.
    • mais si vous le faites, vous devez vous demander si cela est vraiment souhaitable.
    • qu'en est-il du problème de diamant , c'est-à-dire si vous avez plusieurs méthodes dans les délégués avec la même signature?
  • La classe avec délégués (CountingList dans l'exemple ci-dessus) ne sont pas des instances de la classe déléguée.
    • C'est à dire. CountingList n'est pas une instance de LinkedList.
  • Utilisez pour éviter un couplage serré par héritage.

@ 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:

  • Ajoute des méthodes d'une classe à une autre.
  • Utiliser en groovy <2.3 pour un simple ajout de méthodes d'une classe à l'autre
    • ne pas ajouter aux "super" classes (au moins, j'ai eu des problèmes avec ça)
  • Insecte
  • Déconseillé à groovy 2.3
  • Les méthodes implicitement ajoutées sont pas visibles en Java.
  • La classe qui obtient une autre classe mélangée, ne sont pas des instances de cette autre classe
    • C'est à dire. Second n'est pas une instance de First
  • Vous pouvez mélanger plusieurs classes dans une autre classe
    • qu'en est-il du problème de diamant , c'est-à-dire si vous avez des méthodes dans les classes mixtes avec la même signature?
  • Utiliser comme une méthode simple pour ajouter la fonctionnalité d'une classe dans une autre dans groovy <2.3

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:

  • Ajoutez des méthodes à n'importe quelle classe existante
    • toutes les classes du JDK
    • toutes les classes de tiers
    • ou n'importe lequel de vos propres cours
  • Remplace toute méthode existante avec la même signature
  • Les méthodes ajoutées sont pas visibles en Java
  • Généralement utilisé pour étendre les classes existantes/tierces avec de nouvelles fonctionnalités

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:

  • Les traits fournissent une interface avec la mise en œuvre et l'état.
  • Une classe peut implémenter plusieurs traits.
  • Les méthodes implémentées par un trait sont visibles en Java.
  • Compatible avec la vérification de type et la compilation statique.
  • Les traits peuvent implémenter des interfaces.
  • Les traits ne peuvent pas être instanciés par eux-mêmes.
  • Un trait peut prolonger un autre trait.
  • La gestion du problème du diamant est bien définie.
  • Utilisation typique:
    • ajouter des traits similaires à différentes classes.
      • (comme alternative à l'AOP)
    • composer une nouvelle classe à partir de plusieurs traits.
115
Steinar