9. Os Traits
Os Traits são estruturas análogas às classes. No entanto, não é possível instanciá-los. Destinam-se a ser incluídos em classes. A inclusão de um Trait numa classe tem o mesmo efeito que se tivéssemos copiado o código do Trait para a classe. Coloca-se num Trait código que possa ser reutilizado em várias classes.
9.1. A estrutura hierárquica dos scripts

9.2. Inclusão de um Trait numa classe
O script [traits-01.php] mostra uma utilização básica de um Trait numa classe:
<?php
class Class04 {
// atributo
private $name;
// construtor
public function __construct(string $name) {
$this->name = $name;
}
// getters e setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// atributo
private $name;
// getters e setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// inclusão do Trait
use Trait04;
// construtor
public function __construct(string $name) {
$this->name = $name;
}
}
// teste --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// exibição das duas classes
print_r($class04);
print_r($class05);
Comentários ao código
- linhas 3-21: definição da classe [Class04] com um atributo, os seus métodos get/set e um construtor;
- linhas 23-36: retoma-se o código de [Class04] sem o seu construtor e transfere-se-o para o traço [Trait04] tal como está. Não se retoma o construtor, uma vez que um traço não é instanciável;
- linha 23: é a palavra-chave [trait] que faz com que [Trait04] seja um trait em vez de uma classe;
- linhas 38-45: define-se uma classe [Class05] que retoma o código do traço [Trait04] (linha 40) e lhe adiciona um construtor (linhas 43-45), idêntico ao da classe [Class04], para tornar a classe instanciável;
- linha 40: é a palavra-chave [use] que permite a inclusão de um Trait numa classe;
- linhas 50-56: os testes demonstram que as classes [Class04] e [Class05] funcionam da mesma forma;
Resultados
Os resultados das linhas 3-10 mostram que as classes [Class04] e [Class05] têm o mesmo conteúdo;
Conclusão
A utilização da instrução [use Trait] numa classe equivale a incluir o código de [Trait] na classe.
9.3. Utilizar o mesmo padrão em diferentes classes
Uma das principais vantagens do traço parece ser a reutilização do mesmo código (atributos + métodos) entre diferentes classes. Veremos, no entanto, que é possível atingir o mesmo objetivo utilizando classes simples.
A partilha de um trait entre classes é ilustrada pelo seguinte script [trait-02.php]:
<?php
trait Trait01 {
// atributo
private $id = 0;
// método
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusão Trait01
use Trait01 {
// o método [Trait01::doSomething] está acessível
// na classe com o nome [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// método específico da classe
public function doSomething(): void {
// atributo id
$this->id += 10;
// utilização do método Trait01
$this->doSomethingInTrait();
// exibição local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusão de Trait01
use Trait01;
// método local da classe
public function doSomethingElse(): void {
// atributo id
$this->id += 10;
// utilização do método Trait01
$this->doSomething();
// exibição local
print "Class03->doSomethingElse\n";
}
}
// teste01 ----------------
function test01(): void {
$class02 = new Class02();
$class03 = new Class03();
$class02->doSomething();
$class03->doSomethingElse();
}
// teste01
print "test01-----------------\n";
test01();
Comentários
- linhas 3-10: um traço que define um atributo (linha 5) e um método (linhas 8-10).
- O traço [Trait01] é injetado em duas classes: [Class02] (linhas 14-32) e [Class03] (linhas 34-46).
- linhas 16-20: injeção de [Trait01] em [Class02];
- a linha 19 destina-se a resolver um conflito: [Trait01] e [Class02] têm ambos um método denominado [doSomething]. Há dois casos a considerar:
- o método [Class02::doSomething] é chamado a partir do exterior da classe. Neste caso, o método [Class02::doSomething] tem prioridade sobre o método [Trait01::doSomething] e é este que é chamado;
- o método [Class02::doSomething] é chamado a partir do interior da classe. Neste caso, ocorre um conflito: o interpretador PHP não sabe qual o método a chamar;
A linha 19 permite renomear o método [Trait01::doSomething] para [doSomethingInTrait]. Assim, no interior de [Class02], utilizar-se-ão as notações:
- [doSomethingInTrait] para chamar o método [Trait01::doSomething];
- [doSomething] para chamar o método [Class02::doSomething];
- linhas 25, 27: a classe [Class02] utiliza o atributo e o método de [Trait01] como se fossem próprios;
- linhas 34-48: a classe [Class03] é idêntica à classe [Class02]. A inclusão de [Trait01] é aqui mais simples, porque não há conflito entre os métodos de [Trait01] e [Class03];
Resultados
Note-se que não há partilha da característica [Trait01] entre as classes [Class02] e [Class03]. Assim, oatributo [Trait01::i] torna-se, por inclusão de [Trait01] nas classes [Class02] e [Class03], dois atributos diferentes: [Class02::i] e [Class03::i]. É isso que mostram as linhas 2 e 4 dos resultados. Se o atributo [Trait01::i] tivesse sido partilhado entre as classes [Class02] e [Class03], teríamos 20 na linha 4 em vez de 10.
O script [trait-03.php] mostra que é possível chegar ao mesmo resultado utilizando uma classe em vez do traço:
<?php
// classe que substitui o trait
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 {
// inclusão Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método específico da classe
public function doSomething(): void {
// alteração do atributo de Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// utilização do método da Class01
$this->class01->doSomething();
// exibição local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusão da Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método local da classe
public function doSomethingElse(): void {
// alteração do atributo da Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// utilização do método da Class01
$this->class01->doSomething();
// exibição local
print "Class03->doSomethingElse\n";
}
}
// teste01 ----------------
function test01(): void {
// dois objetos
$class02 = new Class02();
$class03 = new Class03();
// vão aceder a duas instâncias diferentes de [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// verificação
$class02->doSomething();
$class03->doSomethingElse();
}
// teste02 ----------------
function test02(): void {
// instância partilhada de [Class01]
$class01 = new Class01();
// dois objetos
$class02 = new Class02();
$class03 = new Class03();
// vão aceder à mesma instância de [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// verificação
$class02->doSomething();
$class03->doSomethingElse();
}
// teste01
print "test01-----------------\n";
test01();
// teste02
print "test02-----------------\n";
test02();
Comentários
- linhas 4-23: a classe [Class01] substitui a classe [Trait01]. O código de [Trait01] estava incorporado no código das classes [Class02] e [Class03]. Neste caso, isso não acontecerá. Será uma referência ao código da classe [Class01] que será inserida nas classes [Class02] e [Class03]. O que significa que os atributos da classe [Class01] ficarão fora do código das classes [Class02] e [Class03]. Como, neste caso, o atributo [id] é privado (linha 6), é necessário definir um getter (linhas 14-16) e um setter (linhas 9-11). Esta é a primeira diferença em relação ao trait: é necessário criar o código de acesso aos atributos privados da classe. Poderíamos ter alterado a visibilidade do atributo [id] para [public], mas nunca é aconselhável fazê-lo. Utilizar um setter para definir o valor de um atributo permite verificar a sua validade;
- linha 27: inclusão no código de [Class02] de uma referência à classe [Class01]. Como este atributo é privado, temos de criar um setter (linhas 30-32) para o inicializar;
- linhas 37-41: para qualquer utilização do código de [Class01], temos de passar pelo atributo [$this→class01];
- linhas 48-69: a classe [Class03] é um clone da classe [Classe02], com a única diferença de que o seu método tem um nome diferente;
- linhas 72-82: o primeiro teste. Este consiste em injetar nas classes [Class02] e [Class03] duas instâncias diferentes da classe [Class01] (linhas 77 e 78);
- linhas 85-97: o segundo teste injeta nas classes [Class02] e [Class03] a mesma instância da classe [Class01] (linhas 97, 92, 93);
- linhas 100-101: execução do teste [test01];
- linhas 103-104: execução do teste [test02];
Resultados
Comentários aos resultados
- linhas 2-5: obtêm-se os mesmos resultados que com o traço [Trait01]. Conclui-se que a utilização do trait não é, neste caso, indispensável, mas que permite uma redução do código, uma vez que não são necessários métodos para aceder aos atributos do trait: estes fazem parte integrante do código no qual o trait foi incorporado;
- linhas 7-10: como se injetou a mesma referência de [Class01] nas classes [Class02] e [Class03], o atributo [Class01::id] foi partilhado entre as duas classes. É por isso que a linha 9 dos resultados apresenta 20 em vez de 10 com o trait. Conclui-se, portanto, que se os atributos do trait tiverem de ser partilhados entre classes, então o trait não é utilizável e é necessário recorrer a uma classe;
9.4. Agrupar métodos num trait
No exemplo anterior, o trait incluía atributos e métodos. Aqui, vamos considerar o caso em que contém apenas métodos. Neste caso, o trait assemelha-se a uma fatoração de métodos que podem, então, ser utilizados em diferentes classes. Como o trait não tem, neste caso, nenhum atributo, vamos considerar o caso em que os métodos que reúne operam exclusivamente sobre os parâmetros que lhes são passados. Na verdade, isso não é obrigatório: um trait pode operar sobre um atributo [$this→attribut1] sem possuir esse atributo. Cabe então às classes que utilizam esse trait fornecer o atributo [$this→attribut1].
No caso de o trait ter apenas métodos que operam exclusivamente sobre os parâmetros que lhes são passados, demonstraremos que o trait pode então ser substituído por uma classe com os mesmos métodos que o trait, declarados como estáticos.
A utilização do trait é ilustrada pelo script [trait-04.php], que retoma o trait do exemplo anterior, removendo-lhe todos os atributos:
<?php
trait Trait01 {
// método a partilhar
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// inclusão Trait01
use Trait01 {
// o método [Trait01::doSomething] está acessível
// na classe com o nome [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// chamada ao método Trait01
$this->doSomethingInTrait();
// exibição local
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusão de Trait01
use Trait01;
// método local da classe
public function doSomethingElse(): void {
// chamada ao método de Trait01
$this->doSomething();
// exibição local
print "Class03->doSomethingElse\n";
}
}
// teste ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comentários
- linhas 3-10: o trait [Trait01] já não tem atributos;
- linhas 14-18: inclusão de [Trait01] em [Class02]. O método de [Trait01] é utilizado na linha 22;
- linha 31: inclusão de [Trait01] em [Class03]. O método de [Trait01] é utilizado na linha 36;
Resultados
Neste caso de utilização, o traço [Trait01] pode ser facilmente substituído por uma classe. Isto é demonstrado pelo seguinte script [trait-05.php]:
<?php
abstract class Class01 {
// método estático a partilhar
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// chamada ao método da Class01
Class01::doSomething();
// exibição local
print "Class02->doSomething\n";
}
}
class Class03 {
// método local da classe
public function doSomethingElse(): void {
// chamada ao método da Class01
Class01::doSomething();
// exibição local
print "Class03->doSomethingElse\n";
}
}
// teste ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comentários
- linhas 3-10: o traço [Trait01] é substituído por uma classe abstrata [Class01], cujos métodos são todos declarados como estáticos. A classe é declarada como abstrata apenas para impedir a sua instanciação. Gostaríamos de ter escrito também [final] para impedir a sua derivação, mas PHP 7 não aceita o prefixo [final abstract] para uma classe. É um ou outro, mas não ambos;
- linha 16: em vez de escrever [$this→doSomethingInTrait], escrevemos agora [Class01::doSomething], ou seja, chamamos o método estático [doSomething] da classe [Class01];
- linha 28: repete-se o mesmo procedimento em [Class03];
Resultados
Temos, de facto, o mesmo resultado que com o traço [Trait01], o que demonstra que a utilização deste pode ser evitada. Referimos que os métodos de um traço podem operar sobre um atributo [$this→attribut1] sem que o traço possua esse atributo. Cabe, então, às classes que utilizam esse traço fornecer o atributo [$this→attribut1]. Trata-se de um caso excêntrico: mais vale «transmitir» o atributo [$this→attribut1], que as classes que utilizam o traço devem possuir, para o próprio traço. Desta forma, ele fará necessariamente parte dos atributos da classe que utiliza o traço.
9.5. Herança múltipla com um traço
É frequente ler na literatura PHP que o traço permitiria a herança múltipla: a possibilidade de uma classe herdar de várias classes. A linguagem C++ possui essa possibilidade, mas não as linguagens Java ou C#, que apenas conhecem a herança simples. Vamos demonstrar que, embora a utilização de um traço numa classe derivada permita implementar algo semelhante à herança múltipla, este caso de utilização pode, mais uma vez, ser implementado com classes simples.
O script [trait-06.php] implementa uma classe derivada e um traço:
<?php
trait Trait01 {
// atributo
private $i;
// método a partilhar
public function doSomethingInTrait01() {
// alteração de Trait01::$i
$this->i++;
// exibição
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// atributo
protected $j = 0;
// método
public function doSomethingInClass02(): void {
// alteração Class02::j
$this->j += 10;
// exibição
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// classe derivada
class Class03 extends Class02 {
// herda de Class02:j e Trait01::i
// inclusão de Trait01
use Trait01;
// método
public function doSomethingInClass03(): void {
// utilização do método de Trait01
$this->doSomethingInTrait01();
// alteração de Trait01::i
$this->i += 100;
// alteração da Class03::j (==Class02::j)
$this->j += 1000;
// visualização
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// teste ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
Comentários
- linhas 3-15: voltamos a um traço [Trait01] com um atributo e um método que o manipula;
- linhas 17-29: uma classe [Class02] que não tem nada a ver com o traço [Trait01]. Ela não o utiliza;
- linha 19: foi declarado o único atributo de [Class02] com visibilidade [protected] para que este seja acessível nas classes derivadas;
- linha 32: a classe [Class03] estende a classe [Class02]. Além disso, incorpora o traço [Trait01] (linha 35). Por fim, herda os atributos e métodos de [Class02] e incorpora os atributos e métodos de [Trait01]. Temos, portanto, algo análogo à herança múltipla;
Resultados
Da mesma forma que foi feito num exemplo anterior, vamos demonstrar que:
- o traço pode ser substituído por uma classe;
- em vez de incorporar o traço na classe derivada, incorpora-se a referência a uma instância da classe;
O script [trait-07.php] é o seguinte:
<?php
class Class01 {
// atributo
protected $i;
// getter e setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// método
public function doSomethingInClass01(): void {
// alteração Class01::$i
$this->i++;
// visualização
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// atributo
protected $j = 0;
// método específico da classe
public function doSomethingInClass02(): void {
// alteração Class02::j
$this->j += 10;
// exibição
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// inclusão da Classe01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// método local da classe
public function doSomethingInClass03(): void {
// utilização do método da Class01
$this->class01->doSomethingInClass01();
// alteração de Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// alteração de Class03::j
$this->j += 1000;
// visualização
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// teste ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();
Comentários
- linhas 3-24: a classe [Class01] substitui o traço [Trait01]. Como a classe [Class01] vai ser incorporada nas classes através de uma referência, foram previstos métodos get/set para o atributo [$i];
- linhas 26-38: a classe [Classe02] não sofre alterações;
- linha 40: a classe [Class03] estende a classe [Classe02];
- linha 42: a classe [Classe01] é incorporada na [Classe03] através de uma referência;
- linhas 45-47: prevê-se uma classe setter para inicializar a referência à classe [Classe01];
- linhas 50-61: o método [doSomethingInClass03] faz o mesmo que anteriormente, mas com um código mais complexo;
Resultados
A partir deste exemplo, pode-se concluir que, mais uma vez, a traço não é indispensável, mas é preciso reconhecer que permite escrever um código mais curto na classe derivada.
9.6. Utilizar um trait em vez de uma classe abstrata
É frequente depararmo-nos com o seguinte caso de utilização: cria-se uma interface I bastante geral que pode dar origem a várias implementações. Estas partilham um código comum, mas diferem noutros métodos. Este caso de utilização pode ser implementado de duas formas:
- cria-se uma classe abstrata C que agrupa o código comum às classes derivadas. A classe C implementa a interface I, mas certos métodos que devem ser declarados nas classes derivadas são, na classe C, declarados como abstratos e, por isso, a própria classe C é abstrata. Em seguida, criam-se as classes C1 e C2, derivadas de C, que implementam, cada uma à sua maneira, os métodos não definidos (abstratos) da sua classe pai C;
- cria-se um traço T quase idêntico à classe abstrata C da solução anterior. Este traço não implementa a interface I, uma vez que, do ponto de vista sintático, não o pode fazer. Em seguida, criam-se as classes C1 e C2, que implementam a interface I e utilizam o traço T. A estas classes resta apenas implementar os métodos da interface I que não foram implementados pelo traço T que importam;
Eis um exemplo que ilustra a grande semelhança entre estas duas soluções.
A aplicação 1 implementa a solução 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 e 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 não implementado
abstract public function doSomethingElse(): void;
}
// classe derivada 1
class Class1 extends AbstractClass {
// atributo
private $attr3 = 13;
// getters e setters
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementação doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// classe 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;
}
// implementação doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// função externa
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// testes
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comentários
- linhas 3-8: a interface [Interface1] tem dois métodos;
- linhas 10-41: a classe abstrata [AbstractClass] implementa a interface [Interface1] (linha 10). Possui dois atributos com os respetivos getters e setters (linhas 12-32), implementa o método [doSomething] da interface [Interface1] (linhas 35-37) mas não sabe implementar o método [doSomethingElse]. Este é, portanto, declarado como abstrato (linha 40). A classe abstrata [AbstractClass] não pode ser instanciada e, para ter utilidade, tem de ser obrigatoriamente derivada;
- linhas 44-63: a classe Class1 estende a classe abstrata [AbstractClass] e, por isso, implementa a interface [Interface1] (linha 14). Ela fornece um corpo ao método [doSomethingElse] que a sua classe pai não tinha definido (linhas 59-61). Adiciona também um atributo aos da sua classe pai (linhas 46-56);
- linhas 66-82: a classe Class2 estende a classe abstrata [AbstractClass] e, por conseguinte, implementa a interface [Interface1] (linha 66). Ela define o corpo do método [doSomethingElse], que a sua classe pai não tinha definido (linhas 80-82). Adiciona também um atributo aos da sua classe pai (linhas 68-77);
- linhas 87-90: a função [useInterfaceWith] recebe como parâmetro um tipo [Interface1] e chama os dois métodos desta interface;
- linhas 93-94: a função [useInterfaceWith] é chamada pela primeira vez com um tipo [Class1] e, pela segunda vez, com um tipo [Class2]. Isto está correto, uma vez que estes dois tipos implementam a interface [Interface1];
Resultados
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Agora, implementamos a solução 2 com o script [trait-09.php]. Isto consiste em substituir a classe abstrata por um trait:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// atributos
private $attr1 = 11;
private $attr2 = 12;
// getters e 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";
}
}
// classe derivada 1
class Class1 implements Interface1 {
// utilização do trait
use Trait1;
// atributo
private $attr3 = 13;
// getters e setters
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementação doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// classe derivada 2
class Class2 implements Interface1 {
// utilização da característica
use Trait1;
// atributo
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementação doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// função externa que utiliza a interface
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// testes
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comentários
- linhas 3-8: a interface [Interface1] não sofreu alterações;
- linhas 10-41: o trait [Trait1] substitui a classe abstrata [AbstractClass] da solução 1. O código é o mesmo, com as seguintes diferenças:
- linha 10: o traço [Trait1] não implementa a interface [Interface1]. Isso é sintaticamente impossível;
- linhas 12-13: o atributo de visibilidade [protected] dos atributos da classe abstrata [AbstractClass] passa aqui a ser [private]. Estes dois atributos têm como objetivo proporcionar às classes derivadas acesso direto aos atributos da classe pai (protected) ou do traço (private) sem ter de passar pelos getters e setters;
- o traço [Trait1] não declara o método abstrato [doSomethingElse];
- linhas 41-62: a classe [Class1] da solução 2 é idêntica à classe [Class1] da solução 1, com as seguintes diferenças:
- linha 41: a classe [Class1] implementa a interface [Interface1], enquanto que na solução 1 estendia a classe abstrata [AbstractClass];
- linha 43: utiliza o traço [Trait1] para implementar uma parte da interface;
- linhas 65-85: podem fazer-se os mesmos comentários que para [Class1];
- linhas 87-95: o resto do código não sofre alterações;
Resultados
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Obtêm-se, de facto, os mesmos resultados.
9.7. Conclusion
Dos exemplos anteriores, depreende-se que os casos de utilização em que o uso do traço traria uma vantagem líquida não são claros. Nos nossos exemplos, é sempre possível prescindir dele, substituindo-o por uma classe. No entanto, parece que a sua utilização é prática para fatorizar código entre diferentes classes derivadas, como se esse código pertencesse a uma classe pai. É isso que faremos num exemplo a seguir.