Fino a pochi minuti fa, per rinfrescarmi un po’ le idee, stavo riguardando alcuni video su Laracasts dedicati ai Design Pattern.
Nello specifico, il video in questione riguardava l’Observer Design Pattern, uno dei più semplici da imparare ed al tempo stesso uno dei più famosi.
Ad un certo punto, il buon Jeffrey Way sforna uno dei suoi ottimi consigli (mi chiedo se gli sia mai capitato di darne di sbagliati): per evitare la duplicazione di codice, usare i trait è sicuramente una buona idea.
Devo ammetterlo: non li ho mai usati più di tanto, se non quasi mai. Così mi sono chiesto: perché non approfondire l’argomento?
Qualche ricerca mi ha portato a leggere svariati articoli, alcuni dei quali (come questo) cercano di dare un’idea complessiva, includendo anche i pro ed i contro.
La cosa molto interessante è proprio il dibattito che si è costruito intorno a questo costrutto, dato che ha coinvolto svariate “scuole di pensiero” riguardo l’ereditarietà multipla, best e bad practice, e così via.
Per cui eccomi qui, a scrivere un articolo per fare un po’ il punto della situazione.
Definizioni, innanzitutto
Cos’è un Trait? Chiediamolo direttamente al manuale di PHP:
Traits is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
ovvero:
I Trait sono un meccanismo per il riuso del codice in linguaggi ad ereditarietà singola, come PHP. L’obiettivo di un Trait è quello di ridurre alcune delle limitazioni dovute a questa tipologia di ereditarietà, permettendo di definire un determinato set di metodi da riusare in più classi che non sono necessariamente nella stessa gerarchia.
In poche parole: i Trait permettono di definire un insieme di metodi in un’entità a parte. Tale entità può essere quindi “condivisa” da altre classi che non necessariamente hanno lo stesso “genitore” o, comunque, sono nella stessa gerarchia.
Approfondiamo il discorso vedendo un veloce esempio per capire meglio di cosa si tratta.
L’Esempio
In un recente articolo, Philip Brown fornisce un ottimo esempio di uso dei trait.
Supponiamo di avere un insieme di entità (che non si trovano nella stessa gerarchia) che condividono una funzionalità di condivisione. Un po’ come avviene su Facebook: ho testi, foto, video e voglio poterli condividere, tramite un’apposita funzione share().
Un’idea è quella di implementare separatamente i vari metodi:
class Post {
public function share($item)
{
return ‘share this item’;
}
}
class Comment {
public function share($item)
{
return ‘share this item’;
}
}
Tuttavia, la funzione di condivisione è praticamente uguale per ogni singolo item… che senso ha duplicarla N volte tante quante sono le entità in gioco?
Ecco che entrano così in scena i trait:
trait Sharable {
public function share($item)
{
return ‘share this item’;
}
}
Esattamente come per una classe astratta, un trait non può essere istanziato, ma viene “incluso” tramite use nella classe che vuole fare uso dei suoi metodi.
Le due classi appena viste, quindi, diventeranno:
class Post {
use Sharable;
}
class Comment {
use Sharable;
}
Provando ad usare le due classi, adesso, il risultato sarà esattamente lo stesso. Abbiamo quindi scritto lo stesso metodo una volta sola, minimizzando la duplicazione di codice.
Sicuramente una bella comodità.
C’è anche da sottolineare un’altra cosa molto importante:
- un trait è diverso da un’interfaccia, perché l’interfaccia definisce un “contratto” a cui una classe deve aderire. Il trait invece dona una determinata funzionalità ad una classe;
- un trait è diverso da una classe astratta, perché esula dai meccanismi dell’ereditarietà. Non c’è bisogno di estendere una classe, basta includere il trait tramite use;
Detta così sembra quasi una manna dal cielo: la risoluzione di tutti i problemi e la promessa di una felicità eterna. Tuttavia…
… non è così per tutti!
No, non lo è.
Alcuni programmatori, infatti, per svariati motivi, hanno deciso di non sostenere l’uso di Trait nei propri progetti.
Uno di questi motivi, sicuramente anche uno dei più validi, è che il Trait rischia facilmente di essere usato male, e di diventare un’altra grande “zozzata”.
Non è un mistero che nella storia di PHP ci sia tanto codice scritto male, che nel corso del tempo ha contribuito in buona parte a “sminuire” il valore stesso del linguaggio.
Inoltre, basta pochissimo ad usare male i trait e creare classi pesanti che infrangono il Single Responsibility Principle. Quindi, massima attenzione.
Quindi? Li uso o non li uso?
Un buon punto di partenza, secondo me, è vedere qualche esempio di “buon uso” dei trait in un’applicazione vera.
Uno dei package consigliati per “esplorare” questo concetto è sicuramente Cashier, di Laravel. Nello specifico, puoi vedere un trait in azione in questo specifico file.
Tu, invece, li usi oppure no?
Sono curioso di sapere cosa ne pensi: se vuoi, fammelo sapere sul forum!