9. I Trait
I Trait sono strutture analoghe alle classi. Tuttavia, non è possibile istanziarli. Sono destinati ad essere inclusi nelle classi. L’inclusione di un Trait in una classe ha lo stesso effetto che si avrebbe copiando il codice del Trait nella classe. In un Trait si inserisce codice che può essere riutilizzato in più classi.
9.1. L'albero degli script

9.2. Inclusione di un tratto in una classe
Lo script [traits-01.php] mostra un utilizzo di base di un tratto in una classe:
<?php
class Class04 {
// attributo
private $name;
// costruttore
public function __construct(string $name) {
$this->name = $name;
}
// getter e setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// attributo
private $name;
// getter e setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// inclusione del Trait
use Trait04;
// costruttore
public function __construct(string $name) {
$this->name = $name;
}
}
// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// visualizzazione delle due classi
print_r($class04);
print_r($class05);
Commenti sul codice
- righe 3-21: definizione della classe [Class04] con un attributo, i relativi metodi get/set e un costruttore;
- righe 23-36: si riprende il codice di [Class04] senza il suo costruttore e lo si trasferisce nel trait [Trait04] così com'è. Non si riprende il costruttore poiché un trait non è istanziabile;
- riga 23: è la parola chiave [trait] che rende [Trait04] un tratto anziché una classe;
- righe 38-45: si definisce una classe [Class05] che riprende il codice del tratto [Trait04] (riga 40) e vi aggiunge un costruttore (righe 43-45), identico a quello della classe [Class04] per rendere la classe istanziabile;
- riga 40: è la parola chiave [use] che consente l’inclusione di un tratto in una classe;
- righe 50-56: alcuni test dimostrano che le classi [Class04] e [Class05] funzionano allo stesso modo;
Risultati
I risultati delle righe 3-10 mostrano che le classi [Class04] e [Class05] hanno lo stesso contenuto;
Conclusione
L'utilizzo dell'istruzione [use Trait] in una classe equivale a includere il codice di [Trait] nella classe.
9.3. Utilizzare lo stesso modello in diverse classi
Uno dei principali vantaggi del tratto sembra essere il riutilizzo dello stesso codice (attributi + metodi) tra classi diverse. Vedremo tuttavia che è possibile raggiungere lo stesso obiettivo utilizzando semplici classi.
La condivisione di un trait tra classi è illustrata dal seguente script [trait-02.php]:
<?php
trait Trait01 {
// attributo
private $id = 0;
// metodo
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusione Trait01
use Trait01 {
// il metodo [Trait01::doSomething] è accessibile
// nella classe con il nome [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// metodo proprio della classe
public function doSomething(): void {
// attributo id
$this->id += 10;
// utilizzo del metodo Trait01
$this->doSomethingInTrait();
// visualizzazione locale
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusione di Trait01
use Trait01;
// metodo locale della classe
public function doSomethingElse(): void {
// attributo id
$this->id += 10;
// utilizzo del metodo di Trait01
$this->doSomething();
// visualizzazione locale
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
$class02 = new Class02();
$class03 = new Class03();
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
Commenti
- righe 3-10: un tratto che definisce un attributo (riga 5) e un metodo (righe 8-10).
- il tratto [Trait01] viene iniettato in due classi: [Class02] (righe 14-32) e [Class03] (righe 34-46).
- righe 16-20: inserimento di [Trait01] in [Class02];
- la riga 19 serve a risolvere un conflitto: sia [Trait01] che [Class02] hanno entrambi un metodo denominato [doSomething]. Si devono prevedere due casi:
- il metodo [Class02::doSomething] viene chiamato dall’esterno della classe. In questo caso, il metodo [Class02::doSomething] ha la priorità sul metodo [Trait01::doSomething] ed è proprio questo che viene chiamato;
- il metodo [Class02::doSomething] viene chiamato dall’interno della classe. In questo caso si verifica un conflitto: l’interprete PHP non sa quale metodo chiamare;
La riga 19 consente di rinominare il metodo [Trait01::doSomething] come [doSomethingInTrait]. Pertanto, all’interno di [Class02] si utilizzeranno le notazioni:
- [doSomethingInTrait] per richiamare il metodo [Trait01::doSomething];
- [doSomething] per chiamare il metodo [Class02::doSomething];
- righe 25, 27: la classe [Class02] utilizza l’attributo e il metodo di [Trait01] come se fossero propri;
- righe 34-48: la classe [Class03] è identica alla classe [Class02]. L’inclusione di [Trait01] è qui più semplice perché non vi è alcuna collisione tra i metodi di [Trait01] e [Class03];
Risultati
Si noti che non vi è condivisione del tratto [Trait01] tra le classi [Class02] e [Class03]. Pertanto, l’attributo [Trait01::i], per inclusione di [Trait01] nelle classi [Class02] e [Class03], diventa due attributi distinti: [Class02::i] e [Class03::i]. È quanto emerge dalle righe 2 e 4 dei risultati. Se l’attributo [Trait01::i] fosse stato condiviso tra le classi [Class02] e [Class03], alla riga 4 avremmo avuto 20 invece di 10.
Lo script [trait-03.php] mostra che è possibile ottenere lo stesso risultato utilizzando una classe al posto del tratto:
<?php
// classe che sostituisce il tratto
class Class01 {
// attributo
private $id = 0;
// setter
public function setId(int $id) {
$this->id = $id;
}
// getter
public function getId(): int {
return $this->id;
}
// metodo
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusione Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// metodo specifico della classe
public function doSomething(): void {
// modifica attributo di Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// utilizzo del metodo di Class01
$this->class01->doSomething();
// visualizzazione locale
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusione di Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// metodo locale della classe
public function doSomethingElse(): void {
// modifica attributo di Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// utilizzo del metodo di Class01
$this->class01->doSomething();
// visualizzazione locale
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
// due oggetti
$class02 = new Class02();
$class03 = new Class03();
// accederanno a due diverse istanze di [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// verifica
$class02->doSomething();
$class03->doSomethingElse();
}
// test02 ----------------
function test02(): void {
// istanza condivisa di [Class01]
$class01 = new Class01();
// due oggetti
$class02 = new Class02();
$class03 = new Class03();
// accederanno alla stessa istanza di [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// verifica
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();
Commenti
- righe 4-23: la classe [Class01] sostituisce la classe [Trait01]. Il codice di [Trait01] era incorporato nel codice delle classi [Class02] e [Class03]. In questo caso, invece, non sarà così. Sarà un riferimento al codice della classe [Class01] che verrà inserito nelle classi [Class02] e [Class03]. Di conseguenza, gli attributi della classe [Class01] saranno esterni al codice delle classi [Class02] e [Class03]. Poiché in questo caso l’attributo [id] è privato (riga 6), è necessario prevedere un getter (righe 14-16) e un setter (righe 9-11). Questa è la prima differenza rispetto al trait: occorre creare il codice per accedere agli attributi privati della classe. Si sarebbe potuto cambiare la visibilità dell’attributo [id] in [public], ma non è mai consigliabile farlo. Passare attraverso un setter per impostare il valore di un attributo permette di verificarne la validità;
- riga 27: inclusione nel codice di [Class02] di un riferimento alla classe [Class01]. Poiché questo attributo è privato, dobbiamo creare un setter (righe 30-32) per inizializzarlo;
- righe 37-41: per qualsiasi utilizzo del codice di [Class01], è necessario passare attraverso l’attributo [$this→class01];
- righe 48-69: la classe [Class03] è un clone della classe [Classe02], tranne per il fatto che il suo metodo ha un nome diverso;
- righe 72-82: il primo test. Consiste nell’iniettare nelle classi [Class02] e [Class03] due istanze diverse della classe [Class01] (righe 77 e 78);
- righe 85-97: il secondo test inserisce nelle classi [Class02] e [Class03] la stessa istanza della classe [Class01] (righe 97, 92, 93);
- righe 100-101: esecuzione del test [test01];
- righe 103-104: esecuzione del test [test02];
Risultati
Commenti sui risultati
- righe 2-5: si ottengono gli stessi risultati del tratto [Trait01]. Se ne deduce che l’uso del trait in questo caso non è indispensabile, ma comporta una riduzione del codice poiché non sono necessari metodi per accedere agli attributi del trait: questi ultimi sono parte integrante del codice in cui il trait è stato incorporato;
- righe 7-10: poiché è stato inserito lo stesso riferimento [Class01] nelle classi [Class02] e [Class03], l’attributo [Class01::id] è stato condiviso tra le due classi. Ecco perché la riga 9 dei risultati mostra 20 invece di 10 con il trait. Ne consegue che se gli attributi del trait devono essere condivisi tra classi, allora il trait non è utilizzabile e occorre ricorrere a una classe;
9.4. Raggruppare i metodi in un trait
Nell’esempio precedente, il trait comprendeva attributi e metodi. Qui consideriamo il caso in cui contenga solo metodi. In questo caso, il trait assomiglia a una fattorizzazione di metodi che è quindi possibile utilizzare in diverse classi. Poiché il trait in questo caso non ha attributi, considereremo il caso in cui i metodi che raggruppa operino esclusivamente sui parametri che vengono loro passati. In realtà, ciò non è obbligatorio: un trait può operare su un attributo [$this→attribut1] senza possedere tale attributo. Spetta quindi alle classi che utilizzano quel trait fornire l’attributo [$this→attribut1].
Nel caso in cui il trait contenga solo metodi che operano esclusivamente sui parametri che vengono loro passati, dimostreremo che il trait può quindi essere sostituito da una classe che abbia gli stessi metodi del trait e che siano dichiarati statici.
L’utilizzo del trait è illustrato dallo script [trait-04.php], che riprende il trait dell’esempio precedente rimuovendone tutti gli attributi:
<?php
trait Trait01 {
// metodo da condividere
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// inclusione Caratteristica01
use Trait01 {
// il metodo [Trait01::doSomething] è accessibile
// nella classe con il nome [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// chiamata del metodo Trait01
$this->doSomethingInTrait();
// visualizzazione locale
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusione di Trait01
use Trait01;
// metodo locale della classe
public function doSomethingElse(): void {
// chiamata al metodo di Trait01
$this->doSomething();
// visualizzazione locale
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Commenti
- righe 3-10: il trait [Trait01] non ha più attributi;
- righe 14-18: inclusione di [Trait01] in [Class02]. Il metodo di [Trait01] viene utilizzato alla riga 22;
- riga 31: inclusione di [Trait01] in [Class03]. Il metodo di [Trait01] viene utilizzato alla riga 36;
Risultati
In questo caso d’uso, il tratto [Trait01] può essere facilmente sostituito da una classe. Ciò è dimostrato dal seguente script [trait-05.php]:
<?php
abstract class Class01 {
// metodo statico da condividere
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// chiamata al metodo di Class01
Class01::doSomething();
// visualizzazione locale
print "Class02->doSomething\n";
}
}
class Class03 {
// metodo locale della classe
public function doSomethingElse(): void {
// chiamata metodo di Class01
Class01::doSomething();
// visualizzazione locale
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Commenti
- righe 3-10: il tratto [Trait01] viene sostituito da una classe astratta [Class01], i cui metodi sono tutti dichiarati statici. La classe è dichiarata astratta esclusivamente per impedirne l’istanziazione. Si sarebbe voluto scrivere anche [final] per impedirne la derivazione, ma PHP 7 non accetta il prefisso [final abstract] per una classe. Si può usare l’uno o l’altro, ma non entrambi;
- riga 16: invece di scrivere [$this→doSomethingInTrait], ora si scrive [Class01::doSomething], ovvero si chiama il metodo statico [doSomething] della classe [Class01];
- riga 28: si ripete la stessa procedura in [Class03];
Risultati
Si ottiene lo stesso risultato che con il tratto [Trait01], il che dimostra che è possibile evitarne l’uso. Abbiamo visto che i metodi di un tratto possono operare su un attributo [$this→attribut1] senza che il tratto stesso possieda tale attributo. Spetta quindi alle classi che utilizzano tale tratto fornire l’attributo [$this→attribut1]. Si tratta di un caso particolare: tanto vale “trasferire” l’attributo [$this→attribut1], che le classi che utilizzano il tratto devono possedere, all’interno del tratto stesso. In questo modo, esso farà necessariamente parte degli attributi della classe che utilizza il tratto.
9.5. Eredità multipla con un tratto
È frequente leggere nella letteratura PHP che il trait consentirebbe l’ereditarietà multipla: la possibilità per una classe di ereditare da più classi. Il linguaggio C++ possiede questa possibilità, ma non i linguaggi Java o C#, che conoscono solo l’ereditarietà singola. Dimostreremo che, sebbene l’uso di un tratto in una classe derivata consenta effettivamente di implementare qualcosa di simile all’ereditarietà multipla, anche in questo caso tale scenario può essere implementato con semplici classi.
Lo script [trait-06.php] implementa una classe derivata e un tratto:
<?php
trait Trait01 {
// attributo
private $i;
// metodo da condividere
public function doSomethingInTrait01() {
// modifica di Trait01::$i
$this->i++;
// visualizzazione
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// attributo
protected $j = 0;
// metodo
public function doSomethingInClass02(): void {
// modifica Class02::j
$this->j += 10;
// visualizzazione
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// classe derivata
class Class03 extends Class02 {
// eredita da Class02:j e Trait01::i
// inclusione di Trait01
use Trait01;
// metodo
public function doSomethingInClass03(): void {
// utilizzo del metodo di Trait01
$this->doSomethingInTrait01();
// modifica di Trait01::i
$this->i += 100;
// modifica di Class03::j (==Class02::j)
$this->j += 1000;
// visualizzazione
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
Commenti
- righe 3-15: si torna a un tratto [Trait01] con un attributo e un metodo che lo gestisce;
- righe 17-29: una classe [Class02] che non ha nulla a che vedere con la proprietà [Trait01]. Non la utilizza;
- riga 19: è stato dichiarato l’unico attributo di [Class02] con visibilità [protected] affinché sia accessibile nelle classi derivate;
- riga 32: la classe [Class03] estende la classe [Class02]. Inoltre, incorpora il tratto [Trait01] (riga 35). Infine, eredita gli attributi e i metodi da [Class02] e incorpora gli attributi e i metodi di [Trait01]. Si tratta quindi di qualcosa di analogo all’ereditarietà multipla;
Risultati
Come già fatto in un esempio precedente, dimostreremo che:
- il tratto può essere sostituito da una classe;
- invece di incorporare il tratto nella classe derivata, si incorpora il riferimento a un’istanza della classe;
Lo script [trait-07.php] è il seguente:
<?php
class Class01 {
// attributo
protected $i;
// getter e setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// metodo
public function doSomethingInClass01(): void {
// modifica Class01::$i
$this->i++;
// visualizzazione
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// attributo
protected $j = 0;
// metodo specifico della classe
public function doSomethingInClass02(): void {
// modifica Class02::j
$this->j += 10;
// visualizzazione
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// inclusione Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// metodo locale della classe
public function doSomethingInClass03(): void {
// utilizzo del metodo di Class01
$this->class01->doSomethingInClass01();
// modifica di Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// modifica di Class03::j
$this->j += 1000;
// visualizzazione
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// test ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();
Commenti
- righe 3-24: la classe [Class01] sostituisce il tratto [Trait01]. Poiché la classe [Class01] verrà incorporata nelle classi tramite un riferimento, sono stati previsti metodi get/set per l’attributo [$i];
- righe 26-38: la classe [Classe02] rimane invariata;
- riga 40: la classe [Class03] estende la classe [Classe02];
- riga 42: la classe [Classe01] è incorporata in [Classe03] tramite un riferimento;
- righe 45-47: si definisce una classe setter per inizializzare il riferimento alla classe [Classe01];
- righe 50-61: il metodo [doSomethingInClass03] esegue la stessa operazione di prima, ma con un codice più complesso;
Risultati
Da questo esempio si può concludere che, anche in questo caso, il trait non è indispensabile, ma bisogna riconoscere che consente di scrivere un codice più conciso nella classe derivata.
9.6. Utilizzare un trait al posto di una classe astratta
Spesso ci si imbatte nel seguente caso d’uso: si crea un’interfaccia I abbastanza generica che può dare origine a diverse implementazioni. Queste condividono un codice comune ma differiscono per altri metodi. È possibile implementare questo caso d’uso in due modi:
- si crea una classe astratta C che raggruppa il codice comune alle classi derivate. La classe C implementa l’interfaccia I, ma alcuni metodi che devono essere dichiarati nelle classi derivate sono dichiarati astratti nella classe C e quindi la classe C è essa stessa astratta. Si creano quindi le classi C1 e C2 derivate da C, che implementano ciascuna a modo proprio i metodi non definiti (astratti) della loro classe padre C;
- si crea un tratto T quasi identico alla classe astratta C della soluzione precedente. Questo tratto non implementa l’interfaccia I poiché sintatticamente non è in grado di farlo. Si creano quindi le classi C1 e C2 che implementano l’interfaccia I e utilizzano il tratto T. A queste classi non resta che implementare i metodi dell’interfaccia I non implementati dal tratto T che importano;
Ecco un esempio che mostra la grande somiglianza tra queste due soluzioni.
L’applicazione 1 implementa la soluzione 1 descritta in precedenza, [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// attributi
protected $attr1 = 11;
protected $attr2 = 12;
// getter e setter
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// metodo implementato
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// metodo non implementato
abstract public function doSomethingElse(): void;
}
// classe derivata 1
class Class1 extends AbstractClass {
// attributo
private $attr3 = 13;
// getter e setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementazione doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// classe derivata 2
class Class2 extends AbstractClass {
// attributo
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementazione doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// funzione esterna
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// test
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Commenti
- righe 3-8: l’interfaccia [Interface1] ha due metodi;
- righe 10-41: la classe astratta [AbstractClass] implementa l'interfaccia [Interface1] (riga 10). Presenta due attributi con i relativi getter e setter (righe 12-32), implementa il metodo [doSomething] dell’interfaccia [Interface1] (righe 35-37) ma non è in grado di implementare il metodo [doSomethingElse]. Quest’ultimo è quindi dichiarato astratto (riga 40). La classe astratta [AbstractClass] non può essere istanziata e, per essere utilizzabile, deve necessariamente essere derivata;
- righe 44-63: la classe Class1 estende la classe astratta [AbstractClass] e quindi implementa l’interfaccia [Interface1] (riga 14). Essa fornisce un corpo al metodo [doSomethingElse] che la sua classe padre non aveva definito (righe 59-61). Aggiunge inoltre un attributo a quelli della sua classe padre (righe 46-56);
- righe 66-82: la classe Class2 estende la classe astratta [AbstractClass] e quindi implementa l’interfaccia [Interface1] (riga 66). Fornisce un corpo al metodo [doSomethingElse] che la sua classe padre non aveva definito (righe 80-82). Aggiunge inoltre un attributo a quelli della sua classe padre (righe 68-77);
- righe 87-90: la funzione [useInterfaceWith] riceve come parametro un tipo [Interface1] e chiama i due metodi di tale interfaccia;
- righe 93-94: la funzione [useInterfaceWith] viene chiamata la prima volta con un tipo [Class1] e la seconda volta con un tipo [Class2]. Questo è corretto poiché entrambi i tipi implementano l’interfaccia [Interface1];
Risultati
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Ora implementiamo la soluzione 2 con lo script [trait-09.php]. Ciò consiste nel sostituire la classe astratta con un tratto:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// attributi
private $attr1 = 11;
private $attr2 = 12;
// getter e setter
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// metodo implementato
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// classe derivata 1
class Class1 implements Interface1 {
// utilizzo del tratto
use Trait1;
// attributo
private $attr3 = 13;
// getter e setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementazione doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// classe derivata 2
class Class2 implements Interface1 {
// utilizzo del tratto
use Trait1;
// attributo
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementazione doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// funzione esterna che utilizza l'interfaccia
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// test
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Commenti
- righe 3-8: l’interfaccia [Interface1] non è cambiata;
- righe 10-41: il trait [Trait1] sostituisce la classe astratta [AbstractClass] della soluzione 1. Il codice è lo stesso, salvo i seguenti dettagli:
- riga 10: il tratto [Trait1] non implementa l’interfaccia [Interface1]. Ciò è sintatticamente impossibile;
- righe 12-13: l’attributo di visibilità [protected] degli attributi della classe astratta [AbstractClass] diventa qui [private]. Questi due attributi hanno lo scopo di fornire alle classi derivate un accesso diretto agli attributi della classe padre (protected) o del tratto (private) senza dover passare attraverso i getter e i setter;
- il tratto [Trait1] non dichiara il metodo astratto [doSomethingElse];
- righe 41-62: la classe [Class1] della soluzione 2 è identica alla classe [Class1] della soluzione 1, salvo i seguenti dettagli:
- riga 41: la classe [Class1] implementa l’interfaccia [Interface1], mentre nella soluzione 1 estendeva la classe astratta [AbstractClass];
- riga 43: utilizza il tratto [Trait1] per implementare una parte dell’interfaccia;
- righe 65-85: si possono fare le stesse osservazioni valide per [Class1];
- righe 87-95: il resto del codice rimane invariato;
Risultati
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Si ottengono effettivamente gli stessi risultati.
9.7. Conclusion
Dagli esempi precedenti emerge che i casi d’uso in cui l’uso del trait comporterebbe un vantaggio netto non sono chiari. Nei nostri esempi è sempre possibile farne a meno, sostituendolo con una classe. Sembra tuttavia che il suo utilizzo sia pratico per fattorizzare il codice tra diverse classi derivate, come se tale codice appartenesse a una classe padre. È proprio ciò che faremo nell’esempio che segue.