Un'introduzione ai tratti in Groovy

1. Panoramica

In questo tutorial, esploreremo il concetto di tratti in Groovy. Sono stati introdotti nella versione 2.3 Groovy.

2. Quali sono i tratti?

I tratti sono componenti riutilizzabili che rappresentano un insieme di metodi o comportamenti che possiamo utilizzare per estendere la funzionalità di più classi.

Per questo motivo, sono considerati come interfacce, che trasportano sia implementazioni predefinite che stato. Tutti i tratti vengono definiti utilizzando la parola chiave trait .

3. Metodi

Dichiarare un metodo in un tratto è simile a dichiarare qualsiasi metodo regolare in una classe. Tuttavia, non possiamo dichiarare metodi protetti o privati ​​del pacchetto in un tratto .

Vediamo come vengono implementati i metodi pubblici e privati.

3.1. Metodi pubblici

Per iniziare, esploreremo come i metodi pubblici vengono implementati in un tratto.

Creiamo un tratto chiamato UserTrait e un metodo sayHello pubblico :

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

Successivamente, creeremo una classe Employee , che implementa UserTrait :

class Employee implements UserTrait {}

Ora, creiamo un test per verificare che un'istanza Employee possa accedere al metodo sayHello di 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. Metodi privati

Possiamo anche creare un metodo privato in un tratto e fare riferimento ad esso in un altro metodo pubblico .

Vediamo l'implementazione del codice in UserTrait:

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

Nota che se accediamo al metodo privato nella classe di implementazione, verrà generata un'eccezione 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: []" }

In un tratto, un metodo privato può essere essenziale per qualsiasi implementazione che non dovrebbe essere sovrascritta da nessuna classe, sebbene richiesta da altri metodi pubblici.

3.3. Metodi astratti

Un tratto può contenere anche metodi astratti che possono essere implementati in un'altra classe:

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

3.4. Sostituzione dei metodi predefiniti

Di solito, un tratto contiene implementazioni predefinite dei suoi metodi pubblici, ma possiamo sovrascriverli nella classe di implementazione:

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

I tratti non supportano gli ambiti protetti e privati .

4. questa parola chiave

Il comportamento della parola chiave this è simile a quello in Java. Possiamo considerare il tratto come una super classe.

Ad esempio, creeremo un metodo che restituisca questo in un tratto :

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

5. Interfacce

Un tratto può anche implementare interfacce, proprio come fanno le classi normali.

Creiamo un'interfaccia e implementiamola in un tratto :

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

Ora implementiamo il metodo astratto dell'interfaccia nella classe di implementazione:

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

6. Proprietà

Possiamo aggiungere proprietà a un tratto proprio come faremmo in qualsiasi classe normale:

trait UserTrait implements Human { String email String address }

7. Estensione dei tratti

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 tratto è un modo davvero efficace per aggiungere implementazioni e funzionalità comuni in tutte le nostre classi. Inoltre, ci consente di ridurre al minimo il codice ridondante e semplifica la manutenzione del codice.

Come al solito, le implementazioni del codice e gli unit test per questo articolo sono disponibili su GitHub.