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

9.2. Inclusão de um Trait em uma classe
O script [traits-01.php] mostra um uso básico de um Trait em uma 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 sobre o código
- linhas 3-21: definição da classe [Class04] com um atributo, seus métodos get/set e um construtor;
- linhas 23-36: retoma-se o código de [Class04] sem seu construtor e transfere-se para o trait [Trait04] tal como está. Não se retoma o construtor, pois um trait 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 adiciona a ele 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 em uma classe;
- linhas 50-56: testes mostram que as classes [Class04] e [Class05] funcionam da mesma maneira;
Resultados
Os resultados das linhas 3-10 mostram que as classes [Class04] e [Class05] têm o mesmo conteúdo;
Conclusão
O uso da instrução [use Trait] em uma classe equivale a incluir o código de [Trait] na classe.
9.3. Usar um mesmo traço em diferentes classes
Um dos principais interesses do traço parece ser a reutilização do mesmo código (atributos + métodos) entre diferentes classes. Veremos, no entanto, que é possível alcançar o mesmo objetivo utilizando classes simples.
O compartilhamento de um trait entre classes é ilustrado 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 próprio 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 bloco que define um atributo (linha 5) e um método (linhas 8-10).
- O trait [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 visa resolver um conflito: [Trait01] e [Class02] possuem, ambos, um método chamado [doSomething]. Há dois casos a serem considerados:
- o método [Class02::doSomething] é chamado de fora da classe. Nesse caso, o método [Class02::doSomething] tem prioridade sobre o método [Trait01::doSomething] e é ele que é chamado;
- o método [Class02::doSomething] é chamado de dentro da classe. Nesse caso, ocorre um conflito: o interpretador PHP não sabe qual método chamar;
A linha 19 permite renomear o método [Trait01::doSomething] como [doSomethingInTrait]. Assim, dentro 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] é mais simples aqui, pois não há conflito entre os métodos de [Trait01] e [Class03];
Resultados
É importante observar que não há compartilhamento da característica [Trait01] entre as classes [Class02] e [Class03]. Assim, oatributo [Trait01::i], por inclusão de [Trait01] nas classes [Class02] e [Class03], torna-se dois atributos diferentes: [Class02::i] e [Class03::i]. É o que mostram as linhas 2 e 4 dos resultados. Se o atributo [Trait01::i] tivesse sido compartilhado 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 usando 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 de atributo da Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// uso 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 de atributo da Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// uso 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();
// acessarão 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 compartilhada de [Class01]
$class01 = new Class01();
// dois objetos
$class02 = new Class02();
$class03 = new Class03();
// acessarão a 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 ao código das classes [Class02] e [Class03]. Aqui, esse não será o caso. Será uma referência ao código da classe [Class01] que será inserida nas classes [Class02] e [Class03]. Isso faz com que os atributos da classe [Class01] fiquem 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). Essa é a primeira diferença em relação ao trait: é preciso criar o código de acesso aos atributos privados da classe. Seria possível alterar a visibilidade do atributo [id] para [public], mas isso nunca é recomendado. Utilizar um setter para definir o valor de um atributo permite verificar sua validade;
- linha 27: inclusão no código de [Class02] de uma referência à classe [Class01]. Como esse atributo é privado, precisamos criar um setter (linhas 30-32) para inicializá-lo;
- linhas 37-41: para qualquer uso do código de [Class01], precisamos passar pelo atributo [$this→class01];
- linhas 48-69: a classe [Class03] é um clone da classe [Classe02], com a única diferença de que seu método tem um nome diferente;
- linhas 72-82: o primeiro teste. Ele 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 sobre os resultados
- linhas 2-5: obtêm-se os mesmos resultados que com o traço [Trait01]. Conclui-se, portanto, que o uso do trait não é indispensável neste caso, mas que ele reduz o código, uma vez que não há necessidade de métodos para acessar os atributos do trait: estes fazem parte integrante do código no qual o trait foi incorporado;
- linhas 7-10: como injetamos a mesma referência de [Class01] nas classes [Class02] e [Class03], o atributo [Class01::id] foi compartilhado entre as duas classes. É por isso que a linha 9 dos resultados exibe 20 em vez de 10 com o trait. Conclui-se, portanto, que, se os atributos do trait precisarem ser compartilhados entre classes, o trait não é utilizável e, nesse caso, deve-se usar uma classe;
9.4. Agrupar métodos em um trait
No exemplo anterior, o trait possuía atributos e métodos. Aqui, consideramos o caso em que ele contém apenas métodos. Nesse caso, o trait se assemelha a uma fatoração de métodos que podem ser utilizados em diferentes classes. Como o trait não possui atributos, vamos considerar o caso em que os métodos que ele 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 em que o trait possua apenas métodos que operam exclusivamente sobre os parâmetros que lhes são passados, mostraremos que o trait pode, então, ser substituído por uma classe que possua os mesmos métodos do trait e que estejam declarados como estáticos.
O uso do trait é ilustrado pelo script [trait-04.php], que retoma o trait do exemplo anterior, removendo todos os atributos:
<?php
trait Trait01 {
// método a ser compartilhado
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 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 {
// chamada do método 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] não possui mais 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 uso, o traço [Trait01] pode ser facilmente substituído por uma classe. Isso é demonstrado pelo seguinte script [trait-05.php]:
<?php
abstract class Class01 {
// método estático a ser compartilhado
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// chamada do 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 de 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 estáticos. A classe é declarada abstrata apenas para impedir sua instanciação. Gostaríamos de ter escrito também [final] para impedir sua derivação, mas PHP 7 não aceita o prefixo [final abstract] para uma classe. É um ou outro, mas não os dois;
- linha 16: em vez de escrever [$this→doSomethingInTrait], agora escrevemos [Class01::doSomething], ou seja, chamamos o método estático [doSomething] da classe [Class01];
- linha 28: repete-se o mesmo procedimento em [Class03];
Resultados
O resultado é exatamente o mesmo que com o traço [Trait01], o que demonstra que o uso deste pode ser evitado. Já foi mencionado 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 incomum: é preferível “elevar” o atributo [$this→attribut1] — que as classes que utilizam o traço devem possuir — para o próprio traço. Dessa forma, ele fará necessariamente parte dos atributos da classe que utiliza o traço.
9.5. Herança múltipla com um traço
É comum ler na literatura que o trait permitiria a herança múltipla: a possibilidade de uma classe herdar de várias outras classes. A linguagem C++ possui essa possibilidade, mas não as linguagens Java ou C#, que conhecem apenas a herança simples. Vamos mostrar que, embora o uso de um trait em uma classe derivada permita implementar algo semelhante à herança múltipla, esse caso de uso também pode 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 ser compartilhado
public function doSomethingInTrait01() {
// modificaçã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 {
// uso do método de Trait01
$this->doSomethingInTrait01();
// modificação de Trait01::i
$this->i += 100;
// modificação de Class03::j (==Class02::j)
$this->j += 1000;
// exibiçã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 trait [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: declarou-se o único atributo de [Class02] com visibilidade [protected] para que ele seja acessível nas classes derivadas;
- linha 32: a classe [Class03] estende a classe [Class02]. Além disso, ela incorpora o traço [Trait01] (linha 35). Por fim, ela 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 em um exemplo anterior, vamos mostrar que:
- o trait pode ser substituído por uma classe;
- em vez de incorporar o trait 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 {
// modificação Class01::$i
$this->i++;
// exibiçã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 {
// modificaçã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 {
// uso do método da Class01
$this->class01->doSomethingInClass01();
// modificação de Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// modificação de Class03::j
$this->j += 1000;
// exibiçã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] será incorporada às classes por meio de uma referência, foram definidos 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 à [Classe03] por meio de uma referência;
- linhas 45-47: define-se uma classe setter para inicializar a referência à classe [Classe01];
- linhas 50-61: o método [doSomethingInClass03] faz o mesmo que anteriormente, porém com um código mais complexo;
Resultados
A partir desse exemplo, pode-se concluir que, mais uma vez, a traço não é indispensável, mas é preciso reconhecer que ela permite escrever um código mais curto na classe derivada.
9.6. Usar um trait em vez de uma classe abstrata
É comum encontrar o seguinte caso de uso: cria-se uma interface I bastante geral que pode dar origem a várias implementações. Essas implementações compartilham um código comum, mas diferem em outros métodos. É possível implementar esse caso de uso de duas maneiras:
- cria-se uma classe abstrata C que reúne 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 declarados como abstratos na classe C e, portanto, 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) de sua classe pai C;
- cria-se um trait T quase idêntico à classe abstrata C da solução anterior. Esse trait não implementa a interface I, pois, sintaticamente, não é possível fazê-lo. Em seguida, criam-se as classes C1 e C2, que implementam a interface I e utilizam o traço T. Essas classes precisam apenas implementar os métodos da interface I que não foram implementados pelo traço T que elas importam;
Aqui está um exemplo que mostra a grande semelhança entre essas 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] possui dois métodos;
- linhas 10-41: a classe abstrata [AbstractClass] implementa a interface [Interface1] (linha 10). Ela possui dois atributos com seus 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]. Portanto, este é declarado como abstrato (linha 40). A classe abstrata [AbstractClass] não pode ser instanciada e, para ter utilidade, deve obrigatoriamente ser derivada;
- linhas 44-63: a classe Class1 estende a classe abstrata [AbstractClass] e, portanto, implementa a interface [Interface1] (linha 14). Ela fornece um corpo para o método [doSomethingElse] que sua classe pai não havia definido (linhas 59-61). Ela também adiciona um atributo aos da sua classe pai (linhas 46-56);
- linhas 66-82: a classe Class2 estende a classe abstrata [AbstractClass] e, portanto, implementa a interface [Interface1] (linha 66). Ela define o corpo do método [doSomethingElse], que sua classe pai não havia definido (linhas 80-82). Ela também adiciona 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 dessa interface;
- linhas 93-94: a função [useInterfaceWith] é chamada pela primeira vez com um tipo [Class1] e, pela segunda vez, com um tipo [Class2]. Isso está correto, pois esses 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 vamos implementar a solução 2 com o script [trait-09.php]. Isso 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 {
// uso 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 {
// uso do traço
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 a ser aqui [private]. Esses dois atributos têm como objetivo proporcionar às classes derivadas acesso direto aos atributos da classe pai (protected) ou do trait (private) sem precisar 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 ela estendia a classe abstrata [AbstractClass];
- linha 43: ela utiliza o traço [Trait1] para implementar uma parte da interface;
- linhas 65-85: podem-se fazer os mesmos comentários que para [Class1];
- linhas 87-95: o restante do código permanece inalterado;
Resultados
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
De fato, obtemos os mesmos resultados.
9.7. Conclusion
A partir dos exemplos anteriores, fica claro que os casos de uso em que o uso do traço traria uma vantagem líquida não são evidentes. Em nossos exemplos, sempre é possível dispensá-lo, substituindo-o por uma classe. No entanto, parece que seu uso é prático para fatorar código entre diferentes classes derivadas, como se esse código pertencesse a uma classe pai. É isso que faremos no exemplo a seguir.