Une introduction aux traits de Groovy

1. Vue d'ensemble

Dans ce didacticiel, nous explorerons le concept de traits dans Groovy. Ils ont été introduits dans la version Groovy 2.3.

2. Quels sont les traits?

Les traits sont des composants réutilisables représentant un ensemble de méthodes ou de comportements que nous pouvons utiliser pour étendre les fonctionnalités de plusieurs classes.

Pour cette raison, ils sont considérés comme des interfaces, portant à la fois les implémentations et l'état par défaut. Tous les traits sont définis à l'aide du mot-clé trait .

3. Méthodes

La déclaration d'une méthode dans un trait est similaire à la déclaration d'une méthode régulière dans une classe. Cependant, nous ne pouvons pas déclarer de méthodes protégées ou privées de package dans un trait .

Voyons comment les méthodes publiques et privées sont implémentées.

3.1. Méthodes publiques

Pour commencer, nous allons explorer comment les méthodes publiques sont implémentées dans un trait.

Créons un trait nommé UserTrait et une méthode publique sayHello :

trait UserTrait { String sayHello() { return "Hello!" } }

Après cela, nous allons créer une classe Employee , qui implémente UserTrait :

class Employee implements UserTrait {}

Maintenant, créons un test pour vérifier qu'une instance Employee peut accéder à la méthode sayHello de UserTrait :

def 'Should return msg string when using Employee.sayHello method provided by User trait' () { when: def msg = employee.sayHello() then: msg msg instanceof String assert msg == "Hello!" }

3.2. Méthodes privées

Nous pouvons également créer une méthode privée dans un trait et y faire référence dans une autre méthode publique .

Voyons l'implémentation du code dans UserTrait:

private String greetingMessage() { return 'Hello, from a private method!' } String greet() { def msg = greetingMessage() println msg return msg } 

Notez que si nous accédons à la méthode privée dans la classe d'implémentation, cela lèvera une MissingMethodException :

def 'Should return MissingMethodException when using Employee.greetingMessage method' () { when: def exception try { employee.greetingMessage() } catch(Exception e) { exception = e } then: exception exception instanceof groovy.lang.MissingMethodException assert exception.message == "No signature of method: com.baeldung.traits.Employee.greetingMessage()" + " is applicable for argument types: () values: []" }

Dans un trait, une méthode privée peut être essentielle pour toute implémentation qui ne doit être remplacée par aucune classe, bien qu'elle soit requise par d'autres méthodes publiques.

3.3. Méthodes abstraites

Un trait peut également contenir des méthodes abstraites qui peuvent ensuite être implémentées dans une autre classe:

trait UserTrait { abstract String name() String showName() { return "Hello, ${name()}!" } }
class Employee implements UserTrait { String name() { return 'Bob' } } 

3.4. Remplacer les méthodes par défaut

Habituellement, un trait contient des implémentations par défaut de ses méthodes publiques, mais nous pouvons les remplacer dans la classe d'implémentation:

trait SpeakingTrait { String speak() { return "Speaking!!" } } 
class Dog implements SpeakingTrait { String speak() { return "Bow Bow!!" } } 

Les traits ne prennent pas en charge les étendues protégées et privées .

4. ce mot - clé

Le comportement du mot-clé this est similaire à celui de Java. On peut considérer le trait comme une super classe.

Par exemple, nous allons créer une méthode qui renvoie ceci dans un trait :

trait UserTrait { def self() { return this } }

5. Interfaces

Un trait peut également implémenter des interfaces, tout comme le font les classes régulières.

Créons une interface et implémentons-la dans un trait :

interface Human { String lastName() }
trait UserTrait implements Human { String showLastName() { return "Hello, ${lastName()}!" } }

Maintenant, implémentons la méthode abstraite de l' interface dans la classe d'implémentation:

class Employee implements UserTrait { String lastName() { return "Marley" } }

6. Propriétés

Nous pouvons ajouter des propriétés à un trait comme nous le ferions dans n'importe quelle classe régulière:

trait UserTrait implements Human { String email String address }

7. Extension des traits

Similar to a regular Groovy class, a trait may extend another trait using the extends keyword:

trait WheelTrait { int noOfWheels } trait VehicleTrait extends WheelTrait { String showWheels() { return "Num of Wheels $noOfWheels" } } class Car implements VehicleTrait {}

We can also extend multiple traits with the implements clause:

trait AddressTrait { String residentialAddress } trait EmailTrait { String email } trait Person implements AddressTrait, EmailTrait {}

8. Multiple Inheritance Conflicts

When a class implements two or more traits that have methods with the same signature, we need to know how to resolve the conflicts. Let's look at how Groovy resolves such conflicts by default, as well as a way that we can override the default resolution.

8.1. Default Conflict Resolution

By default, the method from the last declared trait in the implements clause will be picked up.

Therefore, traits help us to implement multiple inheritances without encountering the Diamond Problem.

First, let's create two traits with a method having the same signature:

trait WalkingTrait { String basicAbility() { return "Walking!!" } } trait SpeakingTrait { String basicAbility() { return "Speaking!!" } } 

Next, let's write a class that implements both traits:

class Dog implements WalkingTrait, SpeakingTrait {} 

Because SpeakingTrait is declared last, its basicAbility method implementation would be picked up by default in the Dog class.

8.2. Explicit Conflict Resolution

Now, if we don't want to simply take the default conflict resolution provided by the language, we can override it byexplicitly choosing which method to call using the trait.super.method reference.

For instance, let's add another method with the same signature to our two traits:

String speakAndWalk() { return "Walk and speak!!" }
String speakAndWalk() { return "Speak and walk!!" }

Now, let's override the default resolution of multiple inheritance conflicts in our Dog class using the super keyword:

class Dog implements WalkingTrait, SpeakingTrait { String speakAndWalk() { WalkingTrait.super.speakAndWalk() } }

9. Implementing Traits at Runtime

To implement a trait dynamically, we can use the as keyword to coerce an object to a trait at runtime.

For instance, let’s create an AnimalTrait with the basicBehavior method:

trait AnimalTrait { String basicBehavior() { return "Animalistic!!" } }

To implement several traits at once, we can use the withTraits method instead of the as keyword:

def dog = new Dog() def dogWithTrait = dog.withTraits SpeakingTrait, WalkingTrait, AnimalTrait

10. Conclusion

In this article, we've seen how to create traits in Groovy and explored some of their useful features.

Un trait est un moyen vraiment efficace d'ajouter des implémentations et des fonctionnalités communes dans toutes nos classes. De plus, cela nous permet de minimiser le code redondant et facilite la maintenance du code.

Comme d'habitude, les implémentations de code et les tests unitaires de cet article sont disponibles à l'adresse over sur GitHub.