9. Traits
Traits sind Strukturen, die Klassen entsprechen. Sie können jedoch nicht instanziiert werden. Sie sind dazu gedacht, in Klassen eingebunden zu werden. Das Einbinden eines Traits in eine Klasse hat denselben Effekt, als wäre der Code des Traits in die Klasse kopiert worden. Wir fügen Code in ein Trait ein, der wahrscheinlich in mehreren Klassen wiederverwendet wird.
9.1. Der Skriptbaum

9.2. Einbinden eines Traits in eine Klasse
Das Skript [traits-01.php] veranschaulicht die grundlegende Verwendung eines Traits in einer Klasse:
<?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);
Code-Kommentare
- Zeilen 3–21: Definition der Klasse [Class04] mit einem Attribut, ihren Get-/Set-Methoden und einem Konstruktor;
- Zeilen 23–36: Wir übernehmen den Code aus [Class04] ohne dessen Konstruktor und übertragen ihn unverändert in das Trait [Trait04]. Wir lassen den Konstruktor weg, da ein Trait nicht instanziiert werden kann;
- Zeile 23: Das Schlüsselwort [trait] macht [Trait04] zu einem Trait statt zu einer Klasse;
- Zeilen 38–45: Wir definieren eine Klasse [Class05], die den Code aus dem Trait [Trait04] (Zeile 40) übernimmt und einen Konstruktor (Zeilen 43–45) hinzufügt, der mit dem der Klasse [Class04] identisch ist, um die Klasse instanziierbar zu machen;
- Zeile 40: Das Schlüsselwort [use] ermöglicht es, ein Trait in eine Klasse einzubinden;
- Zeilen 50–56: Tests zeigen, dass die Klassen [Class04] und [Class05] auf die gleiche Weise funktionieren;
Ergebnisse
Die Ergebnisse in den Zeilen 3–10 zeigen, dass die Klassen [Class04] und [Class05] denselben Inhalt haben;
Schlussfolgerung
Die Verwendung der Anweisung [use Trait] in einer Klasse entspricht der Einbindung des Codes aus [Trait] in die Klasse.
9.3. Verwendung desselben Traits in verschiedenen Klassen
Einer der Hauptvorteile eines Traits scheint die Wiederverwendung desselben Codes (Attribute + Methoden) in verschiedenen Klassen zu sein. Wir werden jedoch sehen, dass wir dasselbe Ziel auch mit einfachen Klassen erreichen können.
Die gemeinsame Nutzung eines Traits durch mehrere Klassen wird durch das folgende Skript [trait-02.php] veranschaulicht:
<?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();
Kommentare
- Zeilen 3–10: Ein Trait, das ein Attribut (Zeile 5) und eine Methode (Zeilen 8–10) definiert.
- Das Trait [Trait01] wird in zwei Klassen injiziert: [Class02] (Zeilen 14–32) und [Class03] (Zeilen 34–46).
- Zeilen 16–20: Einbindung von [Trait01] in [Class02];
- Zeile 19 löst einen Konflikt auf: [Trait01] und [Class02] haben beide eine Methode namens [doSomething]. Es sind zwei Fälle zu berücksichtigen:
- Die Methode [Class02::doSomething] wird von außerhalb der Klasse aufgerufen. In diesem Fall hat die Methode [Class02::doSomething] Vorrang vor der Methode [Trait01::doSomething] und wird aufgerufen;
- Die Methode [Class02::doSomething] wird von innerhalb der Klasse aufgerufen. In diesem Fall liegt ein Konflikt vor: Der PHP-Interpreter weiß nicht, welche Methode aufgerufen werden soll;
In Zeile 19 wird die Methode [Trait01::doSomething] in [doSomethingInTrait] umbenannt. Daher verwenden wir innerhalb von [Class02] die folgende Schreibweise:
- [doSomethingInTrait], um die Methode [Trait01::doSomething] aufzurufen;
- [doSomething], um die Methode [Class02::doSomething] aufzurufen;
- Zeilen 25, 27: Die Klasse [Class02] verwendet das Attribut und die Methode von [Trait01], als wären es ihre eigenen;
- Zeilen 34–48: Die Klasse [Class03] ist identisch mit der Klasse [Class02]. Die Einbindung von [Trait01] ist hier einfacher, da es keinen Konflikt zwischen den Methoden von [Trait01] und [Class03] gibt;
Ergebnisse
Beachten Sie, dass das Merkmal [Trait01] nicht von den Klassen [Class02] und [Class03] gemeinsam genutzt wird. Durch die Einbindung von [Trait01] in die Klassen [Class02] und [Class03] wird das Attribut [Trait01::i] somit zu zwei unterschiedlichen Attributen: [Class02::i] und [Class03::i]. Dies ist in den Zeilen 2 und 4 der Ergebnisse zu sehen. Wäre das Attribut [Trait01::i] zwischen den Klassen [Class02] und [Class03] gemeinsam genutzt worden, hätten wir in Zeile 4 statt 10 den Wert 20 erhalten.
Das Skript [trait-03.php] zeigt, dass wir das gleiche Ergebnis erzielen können, indem wir eine Klasse anstelle eines Traits verwenden:
<?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();
Kommentare
- Zeilen 4–23: Die Klasse [Class01] ersetzt das Trait [Trait01]. Der Code für [Trait01] war in den Code der Klassen [Class02] und [Class03] eingebettet. Hier ist das nicht der Fall. Stattdessen wird ein Verweis auf den Code in der Klasse [Class01] in die Klassen [Class02] und [Class03] eingefügt. Das bedeutet, dass die Attribute der Klasse [Class01] außerhalb des Codes von [Class02] und [Class03] liegen. Da das Attribut [id] hier privat ist (Zeile 6), müssen ein Getter (Zeilen 14–16) und ein Setter (Zeilen 9–11) bereitgestellt werden. Dies ist der erste Unterschied zu einem Trait: Sie müssen den Code erstellen, um auf die privaten Attribute der Klasse zuzugreifen. Wir hätten die Sichtbarkeit des Attributs [id] auf [public] ändern können, dies wird jedoch niemals empfohlen. Die Verwendung eines Setters zum Setzen des Werts eines Attributs ermöglicht es uns, diesen zu validieren;
- Zeile 27: Einfügen eines Verweises auf die Klasse [Class01] in den Code von [Class02]. Da dieses Attribut privat ist, müssen wir einen Setter (Zeilen 30–32) erstellen, um es zu initialisieren;
- Zeilen 37–41: Für jede Verwendung des Codes aus [Class01] müssen wir das Attribut [$this→class01] verwenden;
- Zeilen 48–69: Die Klasse [Class03] ist ein Klon der Klasse [Class02], mit dem Unterschied, dass ihre Methode einen anderen Namen hat;
- Zeilen 72–82: Der erste Test. Bei diesem Test werden zwei verschiedene Instanzen der Klasse [Class01] in die Klassen [Class02] und [Class03] eingefügt (Zeilen 77 und 78);
- Zeilen 85–97: Der zweite Test injiziert dieselbe Instanz der Klasse [Class01] in die Klassen [Class02] und [Class03] (Zeilen 97, 92, 93);
- Zeilen 100–101: Ausführung des Tests [test01];
- Zeilen 103–104: Ausführung des Tests [test02];
Ergebnisse
Kommentare zu den Ergebnissen
- Zeilen 2–5: Wir erhalten dieselben Ergebnisse wie mit dem Trait [Trait01]. Daraus lässt sich schließen, dass die Verwendung des Traits hier nicht unbedingt erforderlich ist, aber den Code reduziert, da keine Methoden für den Zugriff auf die Attribute des Traits benötigt werden: Diese sind integraler Bestandteil des Codes, in den das Trait eingebunden wurde;
- Zeilen 7–10: Da derselbe Verweis auf [Class01] in die Klassen [Class02] und [Class03] injiziert wurde, wurde das Attribut [Class01::id] von beiden Klassen gemeinsam genutzt. Aus diesem Grund wird in Zeile 9 der Ergebnisse bei Verwendung des Traits der Wert 20 statt 10 angezeigt. Wir können daraus schließen, dass, wenn die Attribute eines Traits von mehreren Klassen gemeinsam genutzt werden müssen, das Trait nicht verwendbar ist und stattdessen eine Klasse verwendet werden muss;
9.4. Gruppierung von Methoden in einem Trait
Im vorherigen Beispiel enthielt das Trait Attribute und Methoden. Hier betrachten wir den Fall, in dem es nur Methoden enthält. In diesem Fall ähnelt das Trait einer Faktorisierung von Methoden, die dann in verschiedenen Klassen verwendet werden können. Da das Trait hier keine Attribute hat, betrachten wir den Fall, in dem die darin gruppierten Methoden ausschließlich auf die an sie übergebenen Parameter wirken. Tatsächlich ist dies nicht zwingend erforderlich: Ein Trait kann auf ein Attribut [$this→attribute1] zugreifen, ohne dieses Attribut selbst zu besitzen. Es ist dann Aufgabe der Klassen, die dieses Trait verwenden, das Attribut [$this→attribute1] bereitzustellen.
Für den Fall, dass das Trait nur Methoden enthält, die ausschließlich auf die an sie übergebenen Parameter wirken, werden wir zeigen, dass das Trait dann durch eine Klasse ersetzt werden kann, die dieselben Methoden wie das Trait besitzt und als statisch deklariert ist.
Die Verwendung des Traits wird durch das Skript [trait-04.php] veranschaulicht, das das Trait aus dem vorherigen Beispiel wiederverwendet, dabei jedoch alle Attribute entfernt:
<?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();
Kommentare
- Zeilen 3–10: Das Trait [Trait01] hat keine Attribute mehr;
- Zeilen 14–18: Einbindung von [Trait01] in [Class02]. Die Methode von [Trait01] wird in Zeile 22 verwendet;
- Zeile 31: Einbindung von [Trait01] in [Class03]. Die Methode von [Trait01] wird in Zeile 36 verwendet;
Ergebnisse
In diesem Anwendungsfall kann das Trait [Trait01] problemlos durch eine Klasse ersetzt werden. Dies wird durch das folgende Skript [trait-05.php] veranschaulicht:
<?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();
Kommentare
- Zeilen 3–10: Das Trait [Trait01] wird durch eine abstrakte Klasse [Class01] ersetzt, in der alle Methoden als statisch deklariert sind. Die Klasse wird ausschließlich deshalb als abstrakt deklariert, um ihre Instanziierung zu verhindern. Wir hätten gerne auch [final] hinzugefügt, um ihre Ableitung zu verhindern, aber PHP 7 akzeptiert das Präfix [final abstract] für eine Klasse nicht. Es ist entweder das eine oder das andere, aber nicht beides;
- Zeile 16: Anstatt [$this→doSomethingInTrait] zu schreiben, schreiben wir nun [Class01::doSomething], d. h., wir rufen die statische Methode [doSomething] der Klasse [Class01] auf;
- Zeile 28: Wir wiederholen denselben Vorgang in [Class03];
Ergebnisse
Wir erhalten das gleiche Ergebnis wie mit dem Trait [Trait01], was zeigt, dass dessen Verwendung vermieden werden kann. Wir haben festgestellt, dass die Methoden eines Traits auf ein Attribut [$this→attribut1] zugreifen können, auch wenn das Trait selbst dieses Attribut nicht besitzt. Es liegt daher an den Klassen, die dieses Trait verwenden, das Attribut [$this→attribut1] bereitzustellen. Dies ist ein Sonderfall: Wir könnten das Attribut [$this→attribut1] – über das die Klassen, die das Trait verwenden, verfügen müssen – genauso gut an das Trait selbst „weitergeben“. Auf diese Weise wird es zwangsläufig Teil der Attribute der Klasse, die das Trait verwendet.
9.5. Mehrfachvererbung mit einem Trait
In der PHP-Literatur liest man häufig, dass Traits Mehrfachvererbung ermöglichen: die Fähigkeit einer Klasse, von mehreren Klassen zu erben. Die Sprache C++ unterstützt diese Funktion, Java und C# hingegen nicht, da sie nur Einfachvererbung unterstützen. Wir werden zeigen, dass die Verwendung eines Traits in einer abgeleiteten Klasse zwar die Implementierung von etwas ermöglicht, das der Mehrfachvererbung ähnelt, dieser Anwendungsfall jedoch auch mit einfachen Klassen implementiert werden kann.
Das Skript [trait-06.php] implementiert eine abgeleitete Klasse und ein 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();
Kommentare
- Zeilen 3–15: Wir kehren zu einem Trait [Trait01] mit einem Attribut und einer Methode zurück, die dieses manipuliert;
- Zeilen 17–29: eine Klasse [Class02], die nichts mit dem Trait [Trait01] zu tun hat. Sie verwendet es nicht;
- Zeile 19: Wir haben das einzige Attribut von [Class02] mit der Sichtbarkeit [protected] deklariert, damit es in abgeleiteten Klassen zugänglich ist;
- Zeile 32: Die Klasse [Class03] erweitert die Klasse [Class02]. Außerdem implementiert sie das Trait [Trait01] (Zeile 35). Schließlich erbt sie die Attribute und Methoden von [Class02] und implementiert die Attribute und Methoden von [Trait01]. Wir haben es also mit einer Situation zu tun, die der Mehrfachvererbung ähnelt;
Ergebnisse
Genau wie im vorherigen Beispiel werden wir zeigen, dass:
- das Trait durch eine Klasse ersetzt werden kann;
- anstatt das Trait in die abgeleitete Klasse zu integrieren, integrieren wir einen Verweis auf eine Instanz der Klasse;
Das Skript [trait-07.php] lautet wie folgt:
<?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();
Kommentare
- Zeilen 3–24: Die Klasse [Class01] ersetzt das Trait [Trait01]. Da die Klasse [Class01] über eine Referenz in die Klassen eingebunden wird, wurden Get-/Set-Methoden für das Attribut [$i] bereitgestellt;
- Zeilen 26–38: Die Klasse [Class02] bleibt unverändert;
- Zeile 40: Die Klasse [Class03] erweitert die Klasse [Class02];
- Zeile 42: Die Klasse [Class01] wird über eine Referenz in [Class03] eingebunden;
- Zeilen 45–47: Es wird ein Setter bereitgestellt, um die Referenz auf die Klasse [Class01] zu initialisieren;
- Zeilen 50–61: Die Methode [doSomethingInClass03] führt dieselbe Aufgabe wie zuvor aus, jedoch mit komplexerem Code;
Ergebnisse
Aus diesem Beispiel lässt sich erneut schließen, dass das Trait nicht unbedingt erforderlich ist, aber es muss anerkannt werden, dass es kürzeren Code in der abgeleiteten Klasse ermöglicht.
9.6. Verwendung eines Traits anstelle einer abstrakten Klasse
Häufig begegnen wir folgendem Anwendungsfall: Wir erstellen eine recht allgemeine Schnittstelle I, die zu mehreren Implementierungen führen kann. Diese haben gemeinsamen Code, unterscheiden sich jedoch in anderen Methoden. Wir können diesen Anwendungsfall auf zwei Arten umsetzen:
- Wir erstellen eine abstrakte Klasse C, die den gemeinsamen Code der abgeleiteten Klassen enthält. Die Klasse C implementiert die Schnittstelle I, aber bestimmte Methoden, die in den abgeleiteten Klassen deklariert werden müssen, werden in der Klasse C als abstrakt deklariert, weshalb die Klasse C selbst abstrakt ist. Anschließend erstellen wir die von C abgeleiteten Klassen C1 und C2, von denen jede die undefinierten (abstrakten) Methoden ihrer übergeordneten Klasse C auf ihre eigene Weise implementiert;
- Wir erstellen ein Trait T, das nahezu identisch mit der abstrakten Klasse C aus der vorherigen Lösung ist. Dieses Trait implementiert die Schnittstelle I nicht, da es dies syntaktisch nicht kann. Anschließend erstellen wir die Klassen C1 und C2, die die Schnittstelle I implementieren und das Trait T verwenden. Für diese Klassen bleibt nur noch die Implementierung der Methoden der Schnittstelle I, die nicht durch das von ihnen importierte Trait T implementiert werden;
Hier ist ein Beispiel, das veranschaulicht, wie ähnlich diese beiden Lösungen sind.
Anwendung 1 implementiert die oben beschriebene Lösung 1 [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());
Kommentare
- Zeilen 3–8: Die Schnittstelle [Interface1] verfügt über zwei Methoden;
- Zeilen 10–41: Die abstrakte Klasse [AbstractClass] implementiert die Schnittstelle [Interface1] (Zeile 10). Sie verfügt über zwei Attribute mit ihren Gettern und Settern (Zeilen 12–32), implementiert die Methode [doSomething] der Schnittstelle [Interface1] (Zeilen 35–37), implementiert jedoch nicht die Methode [doSomethingElse]. Diese Methode wird daher als abstrakt deklariert (Zeile 40). Die abstrakte Klasse [AbstractClass] kann nicht instanziiert werden und muss, um nützlich zu sein, abgeleitet werden;
- Zeilen 44–63: Die Klasse „Class1“ erbt von der abstrakten Klasse [AbstractClass] und implementiert somit die Schnittstelle [Interface1] (Zeile 14). Sie liefert einen Methodenkörper für die Methode [doSomethingElse], den ihre übergeordnete Klasse [ ] nicht definiert hatte (Zeilen 59–61). Außerdem fügt sie den Attributen ihrer übergeordneten Klasse ein weiteres hinzu (Zeilen 46–56);
- Zeilen 66–82: Die Klasse Class2 erweitert die abstrakte Klasse [AbstractClass] und implementiert daher die Schnittstelle [Interface1] (Zeile 66). Sie stellt einen Körper für die Methode [doSomethingElse] bereit, den ihre übergeordnete Klasse nicht definiert hatte (Zeilen 80–82). Außerdem fügt sie den Attributen ihrer übergeordneten Klasse ein weiteres hinzu (Zeilen 68–77);
- Zeilen 87–90: Die Funktion [useInterfaceWith] nimmt einen Typ [Interface1] als Parameter entgegen und ruft die beiden Methoden dieser Schnittstelle auf;
- Zeilen 93–94: Die Funktion [useInterfaceWith] wird beim ersten Mal mit einem Typ [Class1] und beim zweiten Mal mit einem Typ [Class2] aufgerufen. Dies ist korrekt, da beide Typen die Schnittstelle [Interface1] implementieren;
Ergebnisse
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Nun implementieren wir Lösung 2 mithilfe des Skripts [trait-09.php]. Dazu muss die abstrakte Klasse durch ein Trait ersetzt werden:
<?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());
Kommentare
- Zeilen 3–8: Die Schnittstelle [Interface1] hat sich nicht geändert;
- Zeilen 10–41: Das Trait [Trait1] ersetzt die abstrakte Klasse [AbstractClass] aus Lösung 1. Der Code ist bis auf die folgenden Details identisch:
- Zeile 10: Das Trait [Trait1] implementiert die Schnittstelle [Interface1] nicht. Dies ist syntaktisch unmöglich;
- Zeilen 12–13: Das Sichtbarkeitsattribut [protected] der Attribute der abstrakten Klasse [AbstractClass] wird hier zu [private]. Diese beiden Attribute sollen abgeleiteten Klassen direkten Zugriff auf die (geschützten) Attribute der übergeordneten Klasse oder die (privaten) Attribute des Traits ermöglichen, ohne dass sie über Getter und Setter gehen müssen;
- Das Trait [Trait1] deklariert die abstrakte Methode [doSomethingElse] nicht;
- Zeilen 41–62: Die Klasse [Class1] in Lösung 2 ist identisch mit der Klasse [Class1] in Lösung 1, mit den folgenden geringfügigen Unterschieden:
- Zeile 41: Die Klasse [Class1] implementiert die Schnittstelle [Interface1], während sie in Lösung 1 die abstrakte Klasse [AbstractClass] erweiterte;
- Zeile 43: Sie verwendet das Trait [Trait1], um einen Teil der Schnittstelle zu implementieren;
- Zeilen 65–85: Es gelten dieselben Anmerkungen wie für [Class1];
- Zeilen 87–95: Der Rest des Codes bleibt unverändert;
Ergebnisse
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Wir erhalten tatsächlich die gleichen Ergebnisse.
9.7. Fazit
Aus den vorangegangenen Beispielen geht klar hervor, dass die Anwendungsfälle, in denen die Verwendung eines Traits einen Netto-Nutzen bieten würde, nicht eindeutig sind. In unseren Beispielen können wir immer darauf verzichten, indem wir es durch eine Klasse ersetzen. Es scheint jedoch, dass die Verwendung eines Traits praktisch ist, um Code zwischen verschiedenen abgeleiteten Klassen auszulagern, so als ob dieser Code zu einer übergeordneten Klasse gehörte. Genau das werden wir im folgenden Beispiel tun.