9. Traits
Os Traits são estruturas análogas às classes. No entanto, não podem ser instanciados. Destinam-se a ser incluídos em classes. Incluir um Trait numa classe tem o mesmo efeito que se o código do Trait tivesse sido copiado para a classe. Colocamos num Trait código que é provável que seja reutilizado em várias classes.
9.1. A árvore de scripts

9.2. Incluir um traço numa classe
O script [traits-01.php] demonstra uma utilização básica de um traço numa 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);
Comentários de 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: retiramos o código de [Class04] sem o seu construtor e transferimo-lo para o trait [Trait04] tal como está. Não incluímos o construtor, uma vez que um trait não pode ser instanciado;
- linha 23: a palavra-chave [trait] torna [Trait04] um trait em vez de uma classe;
- linhas 38–45: definimos uma classe [Class05] que retira o código da característica [Trait04] (linha 40) e adiciona um construtor (linhas 43–45), idêntico ao da classe [Class04], para tornar a classe instanciável;
- linha 40: a palavra-chave [use] permite que uma característica seja incluída numa classe;
- linhas 50–56: os testes mostram que as classes [Class04] e [Class05] funcionam da mesma forma;
Resultados
Os resultados nas linhas 3–10 mostram que as classes [Class04] e [Class05] têm o mesmo conteúdo;
Conclusão
Utilizar a instrução [use Trait] numa classe equivale a incluir o código de [Trait] na classe.
9.3. Utilizar o mesmo Trait em diferentes classes
Uma das principais vantagens de um trait parece ser a reutilização do mesmo código (atributos + métodos) em diferentes classes. Veremos, no entanto, que podemos alcançar o mesmo objetivo utilizando classes simples.
A partilha de um trait entre classes é ilustrada pelo seguinte 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();
Comentários
- Linhas 3–10: uma característica que define um atributo (linha 5) e um método (linhas 8–10).
- A característica [Trait01] é injetada em duas classes: [Class02] (linhas 14–32) e [Class03] (linhas 34–46).
- Linhas 16–20: Injeção de [Trait01] em [Class02];
- A linha 19 resolve um conflito: [Trait01] e [Class02] têm ambos um método chamado [doSomething]. Há dois casos a considerar:
- o método [Class02::doSomething] é chamado de fora da classe. Neste caso, o método [Class02::doSomething] tem precedência sobre o método [Trait01::doSomething] e é o que é chamado;
- o método [Class02::doSomething] é chamado de dentro da classe. Neste caso, há um conflito: o interpretador PHP não sabe qual método chamar;
A linha 19 renomeia o método [Trait01::doSomething] para [doSomethingInTrait]. Assim, dentro de [Class02], utilizaremos a seguinte notação:
- [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 seus;
- linhas 34–48: a classe [Class03] é idêntica à classe [Class02]. A inclusão de [Trait01] é mais simples aqui porque não há conflito entre os métodos de [Trait01] e [Class03];
Resultados
Note-se que a característica [Trait01] não é partilhada entre as classes [Class02] e [Class03]. Assim, ao incluir [Trait01] nas classes [Class02] e [Class03], o atributo [Trait01::i] torna-se dois atributos distintos: [Class02::i] e [Class03::i]. Isto é mostrado nas 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 podemos obter o mesmo resultado utilizando uma classe em vez de um 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();
Comentários
- linhas 4-23: a classe [Class01] substitui o traço [Trait01]. O código para [Trait01] estava incorporado no código das classes [Class02] e [Class03]. Aqui, isso não acontecerá. Em vez disso, será injetada uma referência ao código da classe [Class01] nas classes [Class02] e [Class03]. Isto significa que os atributos da classe [Class01] serão externos ao código das classes [Class02] e [Class03]. Uma vez que o atributo [id] é privado aqui (linha 6), devem ser fornecidos um getter (linhas 14–16) e um setter (linhas 9–11). Esta é a primeira diferença em relação a um trait: é necessário criar o código para aceder aos atributos privados da classe. Poderíamos ter alterado a visibilidade do atributo [id] para [public], mas isso nunca é recomendado. Usar um setter para definir o valor de um atributo permite-nos validá-lo;
- linha 27: inclusão no código [Class02] de uma referência à classe [Class01]. Como este atributo é privado, precisamos de criar um setter (linhas 30–32) para o inicializar;
- linhas 37–41: para qualquer utilização do código da [Class01], devemos utilizar o atributo [$this→class01];
- linhas 48–69: a classe [Class03] é um clone da classe [Class02], exceto que o seu método tem um nome diferente;
- linhas 72–82: o primeiro teste. Este teste envolve a injeção de duas instâncias diferentes da classe [Class01] nas classes [Class02] e [Class03] (linhas 77 e 78);
- linhas 85–97: o segundo teste injeta a mesma instância da classe [Class01] nas classes [Class02] e [Class03] (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: obtemos os mesmos resultados que com o trait [Trait01]. Podemos concluir que a utilização do trait não é essencial aqui, mas reduz o código, pois não há necessidade de métodos para aceder aos atributos do trait: estes são parte integrante do código no qual o trait foi incorporado;
- linhas 7–10: Uma vez que a mesma referência a [Class01] foi injetada 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 quando se utiliza o trait. Podemos concluir que, se os atributos das características precisarem de ser partilhados entre classes, então a característica não é utilizável e deve ser utilizada uma classe em vez disso;
9.4. Agrupamento de métodos num trait
No exemplo anterior, o trait continha atributos e métodos. Aqui, consideramos o caso em que ele 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. Uma vez que o trait não tem atributos aqui, consideraremos o caso em que os métodos que agrupa funcionam exclusivamente com os parâmetros que lhes são passados. Na verdade, isto não é obrigatório: uma característica pode operar sobre um atributo [$this→attribute1] sem possuir esse atributo. Cabe então às classes que utilizam esta característica fornecer o atributo [$this→attribute1].
No caso em que o trait tem apenas métodos que operam exclusivamente sobre os parâmetros que lhes são passados, vamos mostrar que o trait pode então ser substituído por uma classe que tenha os mesmos métodos que o trait e que seja declarada como estática.
A utilização do trait é ilustrada pelo script [trait-04.php], que reutiliza o trait do exemplo anterior, removendo todos os atributos:
<?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();
Comentários
- linhas 3-10: o trait [Trait01] já não tem quaisquer 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 trait [Trait01] pode ser facilmente substituído por uma classe. Isto é demonstrado pelo seguinte 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();
Comentários
- linhas 3-10: o trait [Trait01] é substituído por uma classe abstrata [Class01], na qual todos os métodos são declarados como estáticos. A classe é declarada como abstrata exclusivamente para impedir a sua instanciação. Teríamos gostado de incluir também [final] para impedir a sua derivação, mas o PHP 7 não aceita o prefixo [final abstract] para uma classe. É uma coisa ou outra, mas não ambas;
- 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: repetimos o mesmo processo em [Class03];
Resultados
Obtemos o mesmo resultado que com o trait [Trait01], o que mostra que a sua utilização pode ser evitada. Observámos que os métodos de um trait podem operar sobre um atributo [$this→attribut1] mesmo que o próprio trait não possua esse atributo. Cabe, portanto, às classes que utilizam este trait fornecer o atributo [$this→attribut1]. Este é um caso exótico: poderíamos muito bem “passar” o atributo [$this→attribut1] — que as classes que utilizam o trait devem ter — para o próprio trait. Dessa forma, ele fará necessariamente parte dos atributos da classe que utiliza o trait.
9.5. Herança múltipla com um trait
É comum ler na literatura sobre PHP que os traits permitem a herança múltipla: a capacidade de uma classe herdar de várias classes. A linguagem C++ suporta esta funcionalidade, mas Java e C# não, uma vez que apenas suportam herança única. Iremos mostrar que, embora a utilização de um trait numa classe derivada permita a implementação de algo semelhante à herança múltipla, este caso de utilização também pode ser implementado utilizando classes simples.
O script [trait-06.php] implementa uma classe derivada e um 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();
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 a característica [Trait01]. Ela não a utiliza;
- linha 19: declarámos o único atributo de [Class02] com visibilidade [protected] para que 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
Tal como fizemos no exemplo anterior, vamos mostrar que:
- o trait pode ser substituído por uma classe;
- em vez de incorporar a característica na classe derivada, incorporamos uma referência a uma instância da classe;
O script [trait-07.php] é o seguinte:
<?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();
Comentários
- linhas 3–24: a classe [Class01] substitui o traço [Trait01]. Uma vez que a classe [Class01] será incorporada nas classes através de uma referência, foram fornecidos métodos get/set para o atributo [$i];
- linhas 26–38: a classe [Class02] permanece inalterada;
- linha 40: a classe [Class03] estende a classe [Class02];
- linha 42: a classe [Class01] é incorporada na [Class03] através de uma referência;
- linhas 45–47: é fornecido um setter para inicializar a referência à classe [Class01];
- linhas 50–61: o método [doSomethingInClass03] faz o mesmo que antes, mas com código mais complexo;
Resultados
A partir deste exemplo, podemos concluir que, mais uma vez, o trait não é essencial, mas é preciso reconhecer que permite um código mais curto na classe derivada.
9.6. Usar um trait em vez de uma classe abstrata
Encontramos frequentemente o seguinte caso de uso: criamos uma interface I bastante geral que pode dar origem a várias implementações. Estas partilham código comum, mas diferem noutros métodos. Podemos implementar este caso de uso de duas maneiras:
- Criamos uma classe abstrata C que contém 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. Criamos então as classes C1 e C2 derivadas de C, cada uma das quais implementa os métodos não definidos (abstratos) da sua classe pai C à sua maneira;
- criamos um trait T que é quase idêntico à classe abstrata C da solução anterior. Este trait não implementa a interface I porque, sintaticamente, não o pode fazer. Criamos então as classes C1 e C2 que implementam a interface I e utilizam o trait T. Tudo o que resta a estas classes é implementar os métodos da interface I que não são implementados pelo trait T que importam;
Aqui está um exemplo que ilustra o quão semelhantes estas duas soluções são.
A Aplicação 1 implementa a Solução 1 descrita acima [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());
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 seus getters e setters (linhas 12–32), implementa o método [doSomething] da interface [Interface1] (linhas 35–37), mas não implementa o método [doSomethingElse]. Este método é, portanto, declarado como abstrato (linha 40). A classe abstrata [AbstractClass] não pode ser instanciada e, para ser útil, deve ser derivada de;
- linhas 44–63: a classe Class1 estende a classe abstrata [AbstractClass] e, assim, implementa a interface [Interface1] (linha 14). Ela fornece um corpo para o método [doSomethingElse] que a sua classe pai [ ] não tinha definido (linhas 59–61). Além disso, acrescenta 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 fornece um corpo para o método [doSomethingElse], que a sua classe pai não tinha 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 um tipo [Interface1] como parâmetro 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]. Isto está correto, uma vez que ambos os 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 utilizando o script [trait-09.php]. Isto envolve substituir a classe abstrata por um 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());
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, exceto pelos seguintes detalhes:
- linha 10: o trait [Trait1] não implementa a interface [Interface1]. Isto é sintaticamente impossível;
- linhas 12–13: o atributo de visibilidade [protected] dos atributos da classe abstrata [AbstractClass] passa a ser [private] aqui. Estes dois atributos destinam-se a dar às classes derivadas acesso direto aos atributos da classe pai (protected) ou do trait (private) sem ter de passar por getters e setters;
- o trait [Trait1] não declara o método abstrato [doSomethingElse];
- linhas 41-62: a classe [Class1] na Solução 2 é idêntica à classe [Class1] na Solução 1, com as seguintes pequenas diferenças:
- linha 41: a classe [Class1] implementa a interface [Interface1], enquanto na Solução 1 ela estendia a classe abstrata [AbstractClass];
- linha 43: utiliza o trait [Trait1] para implementar parte da interface;
- linhas 65–85: aplicam-se os mesmos comentários que para [Class1];
- linhas 87–95: o resto 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 facto, obtemos os mesmos resultados.
9.7. Conclusão
A partir dos exemplos anteriores, fica claro que os casos de utilização em que o uso de um trait proporcionaria um benefício líquido não são evidentes. Nos nossos exemplos, podemos sempre passar sem ele, substituindo-o por uma classe. No entanto, parece que o uso de um trait é prático para separar código entre diferentes classes derivadas, como se esse código pertencesse a uma classe pai. É isso que faremos no exemplo seguinte.