9. Los Traits
Los Traits son estructuras análogas a las clases. Sin embargo, no se pueden instanciar. Están destinados a ser incluidos en clases. La inclusión de un Trait en una clase tiene el mismo efecto que si se hubiera copiado el código del Trait en la clase. En un Trait se incluye código susceptible de ser reutilizado en varias clases.
9.1. El árbol de scripts

9.2. Inclusión de un rasgo en una clase
El script [traits-01.php] muestra un uso básico de un Trait en una clase:
<?php
class Class04 {
// atributo
private $name;
// constructor
public function __construct(string $name) {
$this->name = $name;
}
// getters y setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// atributo
private $name;
// getters y setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// inclusión del Trait
use Trait04;
// constructor
public function __construct(string $name) {
$this->name = $name;
}
}
// prueba --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// visualización de las dos clases
print_r($class04);
print_r($class05);
Comentarios del código
- líneas 3-21: definición de la clase [Class04] con un atributo, sus get / set y un constructor;
- líneas 23-36: se toma el código de [Class04] sin su constructor y se transfiere tal cual al trait [Trait04]. No se incluye el constructor, ya que un trait no es instanciable;
- línea 23: es la palabra clave [trait] la que convierte a [Trait04] en un trait en lugar de una clase;
- líneas 38-45: se define una clase [Class05] que retoma el código del trait [Trait04] (línea 40) y le añade un constructor (líneas 43-45), idéntico al de la clase [Class04] para que la clase sea instanciable;
- línea 40: la palabra clave [use] permite la inclusión de un rasgo en una clase;
- líneas 50-56: las pruebas muestran que las clases [Class04] y [Class05] funcionan de la misma manera;
Resultados
Los resultados de las líneas 3-10 muestran que las clases [Class04] y [Class05] tienen el mismo contenido;
Conclusión
El uso de la instrucción [use Trait] en una clase equivale a incluir el código de [Trait] en la clase.
9.3. Usar un mismo trait en diferentes clases
Una de las principales ventajas de la característica parece ser la reutilización del mismo código (atributos + métodos) entre diferentes clases. Sin embargo, veremos que se puede lograr el mismo objetivo utilizando clases simples.
El uso compartido de un trait entre clases se ilustra con el siguiente script [trait-02.php]:
<?php
trait Trait01 {
// atributo
private $id = 0;
// método
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusión Trait01
use Trait01 {
// el método [Trait01::doSomething] es accesible
// en la clase con el nombre [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// método propio de la clase
public function doSomething(): void {
// atributo id
$this->id += 10;
// uso del método de Trait01
$this->doSomethingInTrait();
// visualización local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusión de Trait01
use Trait01;
// método local de la clase
public function doSomethingElse(): void {
// atributo id
$this->id += 10;
// uso del método de Trait01
$this->doSomething();
// visualización local
print "Class03->doSomethingElse\n";
}
}
// prueba01 ----------------
function test01(): void {
$class02 = new Class02();
$class03 = new Class03();
$class02->doSomething();
$class03->doSomethingElse();
}
// prueba01
print "test01-----------------\n";
test01();
Comentarios
- líneas 3-10: un trait que define un atributo (línea 5) y un método (líneas 8-10).
- El rasgo [Trait01] se inyecta en dos clases: [Class02] (líneas 14-32) y [Class03] (líneas 34-46).
- líneas 16-20: inserción de [Trait01] en [Class02];
- la línea 19 tiene como objetivo resolver un conflicto: [Trait01] y [Class02] tienen ambos un método llamado [doSomething]. Hay dos casos que hay que prever:
- el método [Class02::doSomething] se invoca desde fuera de la clase. En este caso, el método [Class02::doSomething] tiene prioridad sobre el método [Trait01::doSomething] y es este el que se invoca;
- el método [Class02::doSomething] se invoca desde dentro de la clase. En este caso hay un conflicto: el intérprete PHP no sabe qué método invocar;
La línea 19 permite renombrar el método [Trait01::doSomething] como [doSomethingInTrait]. Así, dentro de [Class02] se utilizarán las notaciones:
- [doSomethingInTrait] para llamar al método [Trait01::doSomething];
- [doSomething] para llamar al método [Class02::doSomething];
- líneas 25, 27: la clase [Class02] utiliza el atributo y el método de [Trait01] como si fueran propios;
- líneas 34-48: la clase [Class03] es idéntica a la clase [Class02]. La inclusión de [Trait01] es aquí más sencilla porque no hay colisión entre los métodos de [Trait01] y [Class03];
Resultados
Cabe señalar que no hay compartición del rasgo [Trait01] entre las clases [Class02] y [Class03]. Así, elatributo [Trait01::i] se convierte, al incluir [Trait01] en las clases [Class02] y [Class03], en dos atributos diferentes: [Class02::i] y [Class03::i]. Esto es lo que muestran las líneas 2 y 4 de los resultados. Si el atributo [Trait01::i] se hubiera compartido entre las clases [Class02] y [Class03], habríamos tenido 20 en la línea 4 en lugar de 10.
El script [trait-03.php] muestra que se puede llegar al mismo resultado utilizando una clase en lugar del rasgo:
<?php
// clase que sustituye al rasgo
class Class01 {
// atributo
private $id = 0;
// setter
public function setId(int $id) {
$this->id = $id;
}
// getter
public function getId(): int {
return $this->id;
}
// método
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusión Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método propio de la clase
public function doSomething(): void {
// cambio de atributo de Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// uso del método de Class01
$this->class01->doSomething();
// visualización local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusión de Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método local de la clase
public function doSomethingElse(): void {
// cambio de atributo de Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// uso del método de Class01
$this->class01->doSomething();
// visualización local
print "Class03->doSomethingElse\n";
}
}
// prueba01 ----------------
function test01(): void {
// dos objetos
$class02 = new Class02();
$class03 = new Class03();
// accederán a dos instancias diferentes de [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// verificación
$class02->doSomething();
$class03->doSomethingElse();
}
// prueba02 ----------------
function test02(): void {
// instancia compartida de [Class01]
$class01 = new Class01();
// dos objetos
$class02 = new Class02();
$class03 = new Class03();
// accederán a la misma instancia de [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// verificación
$class02->doSomething();
$class03->doSomethingElse();
}
// prueba01
print "test01-----------------\n";
test01();
// prueba02
print "test02-----------------\n";
test02();
Comentarios
- líneas 4-23: la clase [Class01] sustituye a la clase [Trait01]. El código de [Trait01] se incorporaba al código de las clases [Class02] y [Class03]. En este caso, no será así. Se tratará de una referencia al código de la clase [Class01] que se inyectará en las clases [Class02] y [Class03]. Lo que hace que los atributos de la clase [Class01] sean externos al código de [Class02] y [Class03]. Como en este caso el atributo [id] es privado (línea 6), hay que prever un getter (líneas 14-16) y un setter (líneas 9-11). Esta es la primera diferencia con respecto al trait: hay que crear el código de acceso a los atributos privados de la clase. Se podría haber cambiado la visibilidad del atributo [id] a [public], pero nunca es recomendable hacerlo. Utilizar un setter para establecer el valor de un atributo permite verificar su validez;
- línea 27: inclusión en el código de [Class02] de una referencia a la clase [Class01]. Dado que este atributo es privado, debemos crear un setter (líneas 30-32) para inicializarlo;
- líneas 37-41: para cualquier uso del código de [Class01], debemos pasar por el atributo [$this→class01];
- líneas 48-69: la clase [Class03] es un clon de la clase [Classe02], salvo que su método tiene un nombre diferente;
- líneas 72-82: la primera prueba. Consiste en inyectar en las clases [Class02] y [Class03] dos instancias diferentes de la clase [Class01] (líneas 77 y 78);
- líneas 85-97: la segunda prueba inyecta en las clases [Class02] y [Class03] la misma instancia de la clase [Class01] (líneas 97, 92, 93);
- líneas 100-101: ejecución de la prueba [test01];
- líneas 103-104: ejecución de la prueba [test02];
Resultados
Comentarios sobre los resultados
- líneas 2-5: se obtienen los mismos resultados que con el trait [Trait01]. Se deduce que el uso del trait no es aquí indispensable, pero que conlleva una reducción del código debido a que no se necesitan métodos para acceder a los atributos del trait: estos forman parte integrante del código en el que se ha incorporado el trait;
- líneas 7-10: dado que se ha inyectado la misma referencia de [Class01] en las clases [Class02] y [Class03], el atributo [Class01::id] se ha compartido entre ambas clases. Por eso, la línea 9 de los resultados muestra 20 en lugar de 10 con el trait. De ello se deduce que, si los atributos del trait deben compartirse entre clases, entonces el trait no es utilizable y hay que recurrir a una clase;
9.4. Agrupar métodos en un trait
En el ejemplo anterior, el trait contenía atributos y métodos. Aquí consideramos el caso en el que solo contiene métodos. En este caso, el trait se asemeja a una factorización de métodos que luego se pueden utilizar en diferentes clases. Como el trait no tiene aquí ningún atributo, vamos a considerar el caso en el que los métodos que agrupa trabajan únicamente con los parámetros que se les pasan. De hecho, esto no es obligatorio: un trait puede trabajar con un atributo [$this→attribut1] sin tener dicho atributo. En ese caso, son las clases que utilizan ese trait las que deben proporcionar el atributo [$this→attribut1].
En el caso de que el trait solo tenga métodos que operen exclusivamente sobre los parámetros que se les pasan, demostraremos que el trait puede sustituirse por una clase que tenga los mismos métodos que el trait y que estén declarados como estáticos.
El uso del trait se ilustra mediante el script [trait-04.php], que retoma el trait del ejemplo anterior eliminándole todos los atributos:
<?php
trait Trait01 {
// método para compartir
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// inclusión Trait01
use Trait01 {
// el método [Trait01::doSomething] es accesible
// en la clase con el nombre [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// llamada al método de Trait01
$this->doSomethingInTrait();
// visualización local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusión de Trait01
use Trait01;
// método local de la clase
public function doSomethingElse(): void {
// llamada al método de Trait01
$this->doSomething();
// visualización local
print "Class03->doSomethingElse\n";
}
}
// prueba ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comentarios
- líneas 3-10: el trait [Trait01] ya no tiene atributos;
- líneas 14-18: inclusión de [Trait01] en [Class02]. El método de [Trait01] se utiliza en la línea 22;
- línea 31: inclusión de [Trait01] en [Class03]. Se utiliza el método de [Trait01] en la línea 36;
Resultados
En este caso de uso, el rasgo [Trait01] puede sustituirse fácilmente por una clase. Esto se muestra en el siguiente script [trait-05.php]:
<?php
abstract class Class01 {
// método estático para compartir
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// llamada al método de Class01
Class01::doSomething();
// visualización local
print "Class02->doSomething\n";
}
}
class Class03 {
// método local de la clase
public function doSomethingElse(): void {
// llamada al método de Class01
Class01::doSomething();
// visualización local
print "Class03->doSomethingElse\n";
}
}
// prueba ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comentarios
- líneas 3-10: el rasgo [Trait01] se sustituye por una clase abstracta [Class01] cuyos métodos se declaran estáticos. La clase se declara abstracta únicamente para impedir su instanciación. Nos hubiera gustado escribir también [final] para impedir su derivación, pero PHP 7 no acepta el prefijo [final abstract] para una clase. Es uno u otro, pero no ambos;
- línea 16: en lugar de escribir [$this→doSomethingInTrait], ahora escribimos [Class01::doSomething], es decir, llamamos al método estático [doSomething] de la clase [Class01];
- línea 28: se repite el mismo procedimiento en [Class03];
Resultados
Se obtiene el mismo resultado que con el rasgo [Trait01], lo que demuestra que se puede evitar su uso. Se ha escrito que los métodos de un rasgo pueden trabajar sobre un atributo [$this→attribut1] sin que el rasgo tenga dicho atributo. Por lo tanto, son las clases que utilizan este rasgo las que deben proporcionar el atributo [$this→attribut1]. Se trata de un caso poco habitual: es mejor «subir» el atributo [$this→attribut1] que deben tener las clases que utilizan el rasgo, al propio rasgo. De este modo, formará parte necesariamente de los atributos de la clase que utiliza el rasgo.
9.5. Herencia múltiple con un rasgo
Es frecuente leer en la literatura PHP que el trait permitiría la herencia múltiple: la posibilidad de que una clase herede de varias clases. El lenguaje C++ cuenta con esta posibilidad, pero no los lenguajes Java o C#, que solo conocen la herencia simple. Vamos a demostrar que, si bien es cierto que el uso de un trait en una clase derivada permite implementar algo parecido a la herencia múltiple, este caso de uso también se puede implementar con clases simples.
El script [trait-06.php] implementa una clase derivada y un rasgo:
<?php
trait Trait01 {
// atributo
private $i;
// método para compartir
public function doSomethingInTrait01() {
// modificación Trait01::$i
$this->i++;
// visualización
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// atributo
protected $j = 0;
// método
public function doSomethingInClass02(): void {
// modificación Class02::j
$this->j += 10;
// visualización
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// clase derivada
class Class03 extends Class02 {
// hereda de Class02:j y Trait01::i
// inclusión Trait01
use Trait01;
// método
public function doSomethingInClass03(): void {
// uso del método de Trait01
$this->doSomethingInTrait01();
// modificación de Trait01::i
$this->i += 100;
// modificación de Class03::j (==Class02::j)
$this->j += 1000;
// visualización
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// prueba ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
Comentarios
- líneas 3-15: volvemos a un trait [Trait01] con un atributo y un método que lo manipula;
- líneas 17-29: una clase [Class02] que no tiene nada que ver con el rasgo [Trait01]. No lo utiliza;
- línea 19: se ha declarado el único atributo de [Class02] con visibilidad [protected] para que sea accesible en las clases derivadas;
- línea 32: la clase [Class03] hereda de la clase [Class02]. Además, incorpora el rasgo [Trait01] (línea 35). Por último, hereda los atributos y métodos de [Class02] e incorpora los atributos y métodos de [Trait01]. Por lo tanto, se trata de algo análogo a la herencia múltiple;
Resultados
Del mismo modo que se hizo en un ejemplo anterior, vamos a demostrar que:
- el rasgo puede sustituirse por una clase;
- en lugar de incorporar el trait en la clase derivada, se incorpora la referencia de una instancia de la clase;
El script [trait-07.php] es el siguiente:
<?php
class Class01 {
// atributo
protected $i;
// getter y setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// método
public function doSomethingInClass01(): void {
// modificación Class01::$i
$this->i++;
// visualización
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// atributo
protected $j = 0;
// método propio de la clase
public function doSomethingInClass02(): void {
// modificación Class02::j
$this->j += 10;
// visualización
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// inclusión Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método local de la clase
public function doSomethingInClass03(): void {
// uso del método de Class01
$this->class01->doSomethingInClass01();
// modificación de Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// modificación de Class03::j
$this->j += 1000;
// visualización
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// prueba ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();
Comentarios
- líneas 3-24: la clase [Class01] sustituye al rasgo [Trait01]. Dado que la clase [Class01] se incorporará a las clases mediante una referencia, se ha previsto get / set para el atributo [$i];
- líneas 26-38: la clase [Classe02] no cambia;
- línea 40: la clase [Class03] amplía la clase [Classe02];
- línea 42: la clase [Classe01] se incorpora a [Classe03] mediante una referencia;
- líneas 45-47: se define un setter para inicializar la referencia a la clase [Classe01];
- líneas 50-61: el método [doSomethingInClass03] hace lo mismo que antes, aunque con un código más complejo;
Resultados
De este ejemplo se puede concluir que, una vez más, el trait no es indispensable, pero hay que reconocer que permite escribir un código más breve en la clase derivada.
9.6. Utilizar un trait en lugar de una clase abstracta
A menudo nos encontramos con el siguiente caso de uso: se crea una interfaz I bastante general que puede dar lugar a varias implementaciones. Estas comparten un código común, pero difieren en otros métodos. Este caso de uso se puede implementar de dos maneras:
- se crea una clase abstracta C que agrupa el código común a las clases derivadas. La clase C implementa la interfaz I, pero algunos métodos que deben declararse en las clases derivadas se declaran abstractos en la clase C y, por lo tanto, la clase C es en sí misma abstracta. A continuación, se crean las clases C1 y C2 derivadas de C, cada una de las cuales implementa a su manera los métodos no definidos (abstractos) de su clase padre C;
- Se crea un rasgo T casi idéntico a la clase abstracta C de la solución anterior. Este rasgo no implementa la interfaz I, ya que sintácticamente no puede hacerlo. A continuación, se crean las clases C1 y C2, que implementan la interfaz I y utilizan el rasgo T. A estas clases solo les queda implementar los métodos de la interfaz I que no han sido implementados por el rasgo T que importan;
He aquí un ejemplo que muestra la gran similitud entre estas dos soluciones.
La aplicación 1 implementa la solución 1 descrita anteriormente [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// atributos
protected $attr1 = 11;
protected $attr2 = 12;
// getters y 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;
}
// método implementado
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// método no implementado
abstract public function doSomethingElse(): void;
}
// clase derivada 1
class Class1 extends AbstractClass {
// atributo
private $attr3 = 13;
// getter y setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementación doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// clase derivada 2
class Class2 extends AbstractClass {
// atributo
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementación doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// función externa
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// pruebas
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comentarios
- líneas 3-8: la interfaz [Interface1] tiene dos métodos;
- líneas 10-41: la clase abstracta [AbstractClass] implementa la interfaz [Interface1] (línea 10). Tiene dos atributos con sus getter y setter (líneas 12-32), implementa el método [doSomething] de la interfaz [Interface1] (líneas 35-37) pero no sabe implementar el método [doSomethingElse]. Por lo tanto, este se declara abstracto (línea 40). La clase abstracta [AbstractClass] no puede instanciarse y, para que sirva de algo, debe derivarse obligatoriamente;
- líneas 44-63: la clase Class1 hereda de la clase abstracta [AbstractClass] y, por lo tanto, implementa la interfaz [Interface1] (línea 14). Proporciona un cuerpo al método [doSomethingElse] que su clase padre no había definido (líneas 59-61). También añade un atributo a los de su clase padre (líneas 46-56);
- líneas 66-82: la clase Class2 extiende la clase abstracta [AbstractClass] y, por lo tanto, implementa la interfaz [Interface1] (línea 66). Proporciona un cuerpo al método [doSomethingElse] que su clase padre no había definido (líneas 80-82). También añade un atributo a los de su clase padre (líneas 68-77);
- líneas 87-90: la función [useInterfaceWith] recibe como parámetro un tipo [Interface1] y llama a los dos métodos de esta interfaz;
- líneas 93-94: se llama a la función [useInterfaceWith] la primera vez con un tipo [Class1] y la segunda vez con un tipo [Class2]. Esto es correcto, ya que estos dos tipos implementan la interfaz [Interface1];
Resultados
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Ahora implementamos la solución 2 con el script [trait-09.php]. Esto consiste en sustituir la clase abstracta por un trait:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// atributos
private $attr1 = 11;
private $attr2 = 12;
// getters y 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;
}
// método implementado
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// clase derivada 1
class Class1 implements Interface1 {
// uso del trait
use Trait1;
// atributo
private $attr3 = 13;
// getter y setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementación doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// clase derivada 2
class Class2 implements Interface1 {
// uso del rasgo
use Trait1;
// atributo
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementación doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// función externa que utiliza la interfaz
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// pruebas
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comentarios
- líneas 3-8: la interfaz [Interface1] no ha cambiado;
- líneas 10-41: el trait [Trait1] sustituye a la clase abstracta [AbstractClass] de la solución 1. El código es el mismo, salvo por los siguientes detalles:
- línea 10: el rasgo [Trait1] no implementa la interfaz [Interface1]. Esto es sintácticamente imposible;
- líneas 12-13: el atributo de visibilidad [protected] de los atributos de la clase abstracta [AbstractClass] pasa a ser aquí [private]. Estos dos atributos tienen por objeto proporcionar a las clases derivadas acceso directo a los atributos de la clase padre (protected) o del rasgo (private) sin tener que pasar por los getters y setters;
- el rasgo [Trait1] no declara el método abstracto [doSomethingElse];
- líneas 41-62: la clase [Class1] de la solución 2 es idéntica a la clase [Class1] de la solución 1, salvo por los siguientes detalles:
- línea 41: la clase [Class1] implementa la interfaz [Interface1], mientras que en la solución 1 extendía la clase abstracta [AbstractClass];
- línea 43: utiliza el rasgo [Trait1] para implementar una parte de la interfaz;
- líneas 65-85: se pueden hacer los mismos comentarios que para [Class1];
- líneas 87-95: el resto del código no cambia;
Resultados
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Se obtienen los mismos resultados.
9.7. Conclusión
De los ejemplos anteriores se desprende que no están claros los casos en los que el uso del trait aportaría alguna ventaja net. En nuestros ejemplos siempre se puede prescindir de él sustituyéndolo por una clase. Sin embargo, parece que su uso resulta práctico para factorizar código entre diferentes clases derivadas, como si dicho código perteneciera a una clase padre. Esto es lo que haremos en un ejemplo que veremos a continuación.