Skip to content

9. Trait

I tratti sono strutture analoghe alle classi. Tuttavia, non possono essere istanziati. Sono destinati ad essere inclusi nelle classi. Includere un tratto in una classe ha lo stesso effetto che se il codice del tratto fosse stato copiato nella classe. Inseriamo in un tratto il codice che è probabile venga riutilizzato in più classi.

9.1. L'albero degli script

Image

9.2. Inserimento 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 {
  // attribute
  private $name;
 
  // manufacturer
  public function __construct(string $name) {
    $this->name = $name;
  }
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
trait Trait04 {
  // attribute
  private $name;
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
class Class05 {
  // trait inclusion
  use Trait04;
 
  // manufacturer
  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";
// display of both classes
print_r($class04);
print_r($class05);

Commenti al codice

  • righe 3–21: definizione della classe [Class04] con un attributo, i relativi metodi get/set e un costruttore;
  • righe 23–36: prendiamo il codice da [Class04] senza il suo costruttore e lo trasferiamo al trait [Trait04] così com'è. Non includiamo il costruttore poiché un trait non può essere istanziato;
  • riga 23: la parola chiave [trait] rende [Trait04] un trait anziché una classe;
  • righe 38–45: definiamo una classe [Class05] che prende il codice dal trait [Trait04] (riga 40) e aggiunge un costruttore (righe 43–45), identico a quello della classe [Class04], per rendere la classe istanziabile;
  • riga 40: la parola chiave [use] consente di includere un trait in una classe;
  • righe 50–56: i test dimostrano che le classi [Class04] e [Class05] funzionano allo stesso modo;

Risultati

Tim
Burton
Class04 Object
(
    [name:Class04:private] => Tim
)
Class05 Object
(
    [name:Class05:private] => Burton
)

I risultati nelle righe 3–10 mostrano che le classi [Class04] e [Class05] hanno lo stesso contenuto;

Conclusione

L'uso dell'istruzione [use Trait] in una classe equivale a includere il codice di [Trait] nella classe.

9.3. Utilizzo dello stesso trait in classi diverse

Uno dei principali vantaggi di un trait sembra essere il riutilizzo dello stesso codice (attributi + metodi) in classi diverse. Vedremo, tuttavia, che possiamo raggiungere lo stesso obiettivo utilizzando semplici classi.

La condivisione di un trait tra classi è illustrata dal seguente script [trait-02.php]:


<?php
 
trait Trait01 {
  // attribute
  private $id = 0;
 
  // method
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  // class-specific method
  public function doSomething(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomething();
    // local display
    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 trait che definisce un attributo (riga 5) e un metodo (righe 8–10).
  • Il trait [Trait01] viene iniettato in due classi: [Class02] (righe 14–32) e [Class03] (righe 34–46).
  • Righe 16–20: Iniezione di [Trait01] in [Class02];
  • La riga 19 risolve un conflitto: [Trait01] e [Class02] hanno entrambi un metodo denominato [doSomething]. Ci sono due casi da considerare:
    • il metodo [Class02::doSomething] viene chiamato dall'esterno della classe. In questo caso, il metodo [Class02::doSomething] ha la precedenza sul metodo [Trait01::doSomething] ed è quello che viene chiamato;
    • il metodo [Class02::doSomething] viene chiamato dall'interno della classe. In questo caso, c'è un conflitto: l'interprete PHP non sa quale metodo chiamare;

La riga 19 rinomina il metodo [Trait01::doSomething] in [doSomethingInTrait]. Pertanto, all'interno di [Class02], useremo la seguente notazione:

  • [doSomethingInTrait] per chiamare 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] è più semplice in questo caso perché non vi è alcun conflitto tra i metodi di [Trait01] e [Class03];

Risultati

1
2
3
4
5
test01-----------------
Trait01::doSomething… (10)
Class02->doSomething
Trait01::doSomething… (10)
Class03->doSomethingElse

Si noti che il tratto [Trait01] non è condiviso tra le classi [Class02] e [Class03]. Pertanto, includendo [Trait01] nelle classi [Class02] e [Class03], l'attributo [Trait01::i] diventa due attributi distinti: [Class02::i] e [Class03::i]. Ciò è mostrato nelle righe 2 e 4 dei risultati. Se l'attributo [Trait01::i] fosse stato condiviso tra le classi [Class02] e [Class03], avremmo avuto 20 nella riga 4 invece di 10.

Lo script [trait-03.php] mostra che possiamo ottenere lo stesso risultato utilizzando una classe invece di un trait:


<?php
 
// class that replaces the
class Class01 {
  // attribute
  private $id = 0;
 
  // setter
  public function setId(int $id) {
    $this->id = $id;
  }
 
  // getter
  public function getId(): int {
    return $this->id;
  }
 
  // method
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // class-specific method
  public function doSomething(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingElse(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test01 ----------------
function test01(): void {
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access two different instances of [Class01]
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test02 ----------------
function test02(): void {
  // shared instance of [Class01]
  $class01 = new Class01();
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access the same instance of [Class01]
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();

Commenti

  • righe 4-23: la classe [Class01] sostituisce il tratto [Trait01]. Il codice per [Trait01] era incorporato nel codice delle classi [Class02] e [Class03]. Qui, non sarà così. Invece, un riferimento al codice nella classe [Class01] verrà iniettato nelle classi [Class02] e [Class03]. Ciò significa che gli attributi della classe [Class01] saranno esterni al codice di [Class02] e [Class03]. Poiché l'attributo [id] è privato in questo caso (riga 6), è necessario fornire un getter (righe 14–16) e un setter (righe 9–11). Questa è la prima differenza rispetto a un trait: è necessario creare il codice per accedere agli attributi privati della classe. Avremmo potuto cambiare la visibilità dell'attributo [id] in [public], ma questo non è mai consigliato. L'uso di un setter per impostare il valore di un attributo ci permette di convalidarlo;
  • riga 27: inclusione nel codice [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], dobbiamo usare l'attributo [$this→class01];
  • righe 48–69: la classe [Class03] è un clone della classe [Class02], tranne per il fatto che il suo metodo ha un nome diverso;
  • righe 72–82: il primo test. Questo test prevede l'inserimento di due diverse istanze della classe [Class01] nelle classi [Class02] e [Class03] (righe 77 e 78);
  • righe 85–97: il secondo test inietta la stessa istanza della classe [Class01] nelle classi [Class02] e [Class03] (righe 97, 92, 93);
  • righe 100–101: esecuzione del test [test01];
  • righe 103–104: esecuzione del test [test02];

Risultati

test01-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (10)
Class03->doSomethingElse
test02-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (20)
Class03->doSomethingElse

Commenti sui risultati

  • righe 2–5: otteniamo gli stessi risultati del trait [Trait01]. Possiamo concludere che l'uso del trait non è essenziale in questo caso, ma riduce il codice perché non servono metodi per accedere agli attributi del trait: questi sono parte integrante del codice in cui il trait è stato incorporato;
  • righe 7–10: poiché lo stesso riferimento a [Class01] è stato iniettato nelle classi [Class02] e [Class03], l'attributo [Class01::id] è stato condiviso tra le due classi. Questo è il motivo per cui la riga 9 dei risultati mostra 20 invece di 10 quando si utilizza il trait. Possiamo concludere che se gli attributi dei trait devono essere condivisi tra le classi, allora il trait non è utilizzabile e deve essere utilizzata invece una classe;

9.4. Raggruppamento di metodi in un trait

Nell'esempio precedente, il trait conteneva attributi e metodi. Qui, consideriamo il caso in cui contenga solo metodi. In questo caso, il trait assomiglia a una fattorizzazione di metodi che possono poi essere utilizzati in diverse classi. Poiché il trait qui non ha attributi, considereremo il caso in cui i metodi che raggruppa operino esclusivamente sui parametri che vengono loro passati. In realtà, questo non è obbligatorio: un trait può operare su un attributo [$this→attribute1] senza possedere esso stesso quell'attributo. Spetta quindi alle classi che utilizzano questo trait fornire l'attributo [$this→attribute1].

Nel caso in cui il trait abbia solo metodi che operano esclusivamente sui parametri che gli vengono passati, mostreremo che il trait può quindi essere sostituito da una classe che abbia gli stessi metodi del trait e che sia dichiarata statica.

L'uso del trait è illustrato dallo script [trait-04.php], che riutilizza il trait dell'esempio precedente rimuovendo tutti gli attributi:


<?php
 
trait Trait01 {
 
  // method to share
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  public function doSomething(): void {
    // trait01 method call
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // trait01 method call
    $this->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Commenti

  • righe 3-10: il trait [Trait01] non ha più alcun attributo;
  • 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

1
2
3
4
Trait01::doSomething ….
Class02->doSomething
Trait01::doSomething ….
Class03->doSomethingElse

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 {
 
  // static method to share
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }
 
}
 
class Class02 {

  public function doSomething(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
 
  // method local to the class
  public function doSomethingElse(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Commenti

  • righe 3-10: il trait [Trait01] viene sostituito da una classe astratta [Class01] in cui tutti i metodi sono dichiarati statici. La classe è dichiarata astratta esclusivamente per impedirne l'istanziazione. Avremmo voluto aggiungere 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 scriviamo [Class01::doSomething], ovvero chiamiamo il metodo statico [doSomething] della classe [Class01];
  • riga 28: ripetiamo lo stesso processo in [Class03];

Risultati

1
2
3
4
Class01::doSomething ….
Class02->doSomething
Class01::doSomething ….
Class03->doSomethingElse

Otteniamo lo stesso risultato del trait [Trait01], il che dimostra che è possibile evitarne l’uso. Abbiamo notato che i metodi di un trait possono operare su un attributo [$this→attribut1] anche se il trait stesso non possiede quell’attributo. Spetta quindi alle classi che utilizzano questo trait fornire l'attributo [$this→attribut1]. Si tratta di un caso particolare: potremmo anche "trasferire" l'attributo [$this→attribut1] — che le classi che utilizzano il trait devono avere — nel trait stesso. In questo modo, esso farà necessariamente parte degli attributi della classe che utilizza il trait.

9.5. Eredità multipla con un trait

È comune leggere nella letteratura su PHP che i trait consentono l'ereditarietà multipla: la capacità di una classe di ereditare da più classi. Il linguaggio C++ supporta questa funzionalità, ma Java e C# no, poiché supportano solo l'ereditarietà singola. Mostreremo che, sebbene l'uso di un trait in una classe derivata consenta l'implementazione di qualcosa di simile all'ereditarietà multipla, questo caso d'uso può essere implementato anche utilizzando semplici classi.

Lo script [trait-06.php] implementa una classe derivata e un trait:


<?php
 
trait Trait01 {
  // attribute
  private $i;
 
  // method to share
  public function doSomethingInTrait01() {
    // modification Trait01::$i
    $this->i++;
    // display
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
// derived class
class Class03 extends Class02 {
  // inherits Class02:j and Trait01::i
  // inclusion Trait01
  use Trait01;
 
  // method
  public function doSomethingInClass03(): void {
    // using the Trait01 method
    $this->doSomethingInTrait01();
    // modification Trait01::i
    $this->i += 100;
    // modification Class03::j (==Class02::j)
    $this->j += 1000;
    // display
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

Commenti

  • righe 3-15: torniamo a un trait [Trait01] con un attributo e un metodo che lo manipola;
  • righe 17–29: una classe [Class02] che non ha nulla a che vedere con il tratto [Trait01]. Non lo utilizza;
  • riga 19: abbiamo dichiarato l'unico attributo di [Class02] con visibilità [protected] in modo che 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 di [Class02] e incorpora gli attributi e i metodi di [Trait01]. Abbiamo quindi qualcosa di analogo all'ereditarietà multipla;

Risultati

1
2
3
Class02->doSomethingInClass02… j=10
Trait01::doSomethingInTrait01… i=1
Class03->doSomethingInClass03… i=101, j=1000

Proprio come abbiamo fatto nell'esempio precedente, dimostreremo che:

  • il tratto può essere sostituito da una classe;
  • invece di incorporare il tratto nella classe derivata, incorporiamo un riferimento a un'istanza della classe;

Lo script [trait-07.php] è il seguente:


<?php
 
class Class01 {
  // attribute
  protected $i;
 
  // getter and setter
  public function getI(): int {
    return $this->i;
  }
 
  public function setI(int $i): void {
    $this->i = $i;
  }
 
  // method
  public function doSomethingInClass01(): void {
    // modification Class01::$i
    $this->i++;
    // display
    print "Class01::doSomething in Class01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // class-specific method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
class Class03 extends Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingInClass03(): void {
    // using the Class01 method
    $this->class01->doSomethingInClass01();
    // modification Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // modification Class03::j
    $this->j += 1000;
    // display
    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 trait [Trait01]. Poiché la classe [Class01] verrà incorporata nelle classi tramite un riferimento, sono stati forniti metodi get/set per l'attributo [$i];
  • righe 26–38: la classe [Class02] rimane invariata;
  • riga 40: la classe [Class03] estende la classe [Class02];
  • riga 42: la classe [Class01] viene incorporata in [Class03] tramite un riferimento;
  • righe 45–47: viene fornito un setter per inizializzare il riferimento alla classe [Class01];
  • righe 50–61: il metodo [doSomethingInClass03] fa la stessa cosa di prima, ma con un codice più complesso;

Risultati

1
2
3
Class02->doSomethingInClass02… j=10
Class01::doSomething in Class01… i=1
Class03->doSomethingInClass03… i=101, j=1000

Da questo esempio possiamo concludere che, ancora una volta, il trait non è essenziale, ma bisogna riconoscere che permette di scrivere codice più breve nella classe derivata.

9.6. Utilizzo di un trait al posto di una classe astratta

Spesso ci troviamo di fronte al seguente caso d'uso: creiamo un'interfaccia I abbastanza generica che può dare origine a diverse implementazioni. Queste condividono codice comune ma differiscono in altri metodi. Possiamo implementare questo caso d'uso in due modi:

  1. Creiamo una classe astratta C che contiene 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 stessa è astratta. Creiamo quindi le classi C1 e C2 derivate da C, ciascuna delle quali implementa a modo proprio i metodi non definiti (astratti) della propria classe padre C;
  2. creiamo un trait T che è quasi identico alla classe astratta C della soluzione precedente. Questo trait non implementa l'interfaccia I perché, sintatticamente, non può farlo. Creiamo quindi le classi C1 e C2 che implementano l'interfaccia I e utilizzano il trait T. A queste classi non resta che implementare i metodi dell'interfaccia I che non sono implementati dal trait T che importano;

Ecco un esempio che illustra quanto siano simili queste due soluzioni.

L'applicazione 1 implementa la Soluzione 1 descritta sopra [trait-08.php]:


<?php

interface Interface1 {
 
  public function doSomething(): void;
 
  public function doSomethingElse(): void;
}
 
abstract class AbstractClass implements Interface1 {
  // attributes
  protected $attr1 = 11;
  protected $attr2 = 12;
 
  // getters and setters
  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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }
 
  // method not implemented
  abstract public function doSomethingElse(): void;
}
 
// derived class 1
class Class1 extends AbstractClass {
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 extends AbstractClass {
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
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). Ha due attributi con i relativi getter e setter (righe 12–32), implementa il metodo [doSomething] dell'interfaccia [Interface1] (righe 35–37), ma non implementa il metodo [doSomethingElse]. Questo metodo è quindi dichiarato astratto (riga 40). La classe astratta [AbstractClass] non può essere istanziata e, per essere utile, deve essere derivata da;
  • righe 44–63: la classe Class1 estende la classe astratta [AbstractClass] e quindi implementa l'interfaccia [Interface1] (riga 14). Fornisce un corpo per il 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 per il 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] accetta un tipo [Interface1] come parametro 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]. Ciò è 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 utilizzando lo script [trait-09.php]. Ciò comporta la sostituzione della classe astratta con un trait:


<?php
 
interface Interface1 {
 
  public function doSomething(): void;
 
  public function doSomethingElse(): void;
}
 
trait Trait1 {
  // attributes
  private $attr1 = 11;
  private $attr2 = 12;
 
  // getters and setters
  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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}
 
// derived class 1
class Class1 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function using the
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
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 tranne che per 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 l'accesso diretto agli attributi della classe padre (protected) o del tratto (private) senza dover passare attraverso getter e setter;
    • il trait [Trait1] non dichiara il metodo astratto [doSomethingElse];
  • righe 41-62: la classe [Class1] nella Soluzione 2 è identica alla classe [Class1] nella Soluzione 1, con le seguenti piccole differenze:
    • riga 41: la classe [Class1] implementa l'interfaccia [Interface1], mentre nella Soluzione 1 estendeva la classe astratta [AbstractClass];
    • riga 43: utilizza il trait [Trait1] per implementare parte dell'interfaccia;
  • righe 65–85: valgono le stesse osservazioni relative a [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]

Otteniamo effettivamente gli stessi risultati.

9.7. Conclusione

Dagli esempi precedenti, è chiaro che i casi d'uso in cui l'utilizzo di un trait fornirebbe un vantaggio netto non sono chiari. Nei nostri esempi, possiamo sempre farne a meno sostituendolo con una classe. Tuttavia, sembra che l'uso di un trait sia pratico per estrapolare il codice tra diverse classi derivate, come se quel codice appartenesse a una classe padre. Questo è ciò che faremo nell'esempio seguente.