9. Traits
Traits sind Strukturen, die Klassen ähneln. Sie können jedoch nicht instanziiert werden. Sie sind dazu bestimmt, in Klassen eingebunden zu werden. Das Einbinden eines Traits in eine Klasse hat denselben Effekt, als hätte man den Code des Traits in die Klasse kopiert. In ein Trait wird Code aufgenommen, der in mehreren Klassen wiederverwendet werden kann.
9.1. Die Skript-Struktur

9.2. Einbindung eines Traits in eine Klasse
Das Skript [traits-01.php] zeigt eine grundlegende Verwendung eines Traits in einer Klasse:
<?php
class Class04 {
// Attribut
private $name;
// Konstruktor
public function __construct(string $name) {
$this->name = $name;
}
// Getter und Setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// Attribut
private $name;
// Getter und Setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// Einbindung des Traits
use Trait04;
// Konstruktor
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";
// Anzeige der beiden Klassen
print_r($class04);
print_r($class05);
Kommentare zum Code
- Zeilen 3–21: Definition der Klasse „[Class04]“ mit einem Attribut, ihren Get-/Set-Methoden und einem Konstruktor;
- Zeilen 23–36: Der Code von [Class04] wird ohne dessen Konstruktor übernommen und unverändert in den Trait [Trait04] übertragen. Der Konstruktor wird nicht übernommen, da ein Trait nicht instanziierbar ist;
- Zeile 23: Das Schlüsselwort [trait] macht aus [Trait04] einen Trait statt einer Klasse;
- Zeilen 38–45: Es wird eine Klasse [Class05] definiert, die den Code des Traits [Trait04] (Zeile 40) übernimmt und ihm 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 die Einbindung eines Traits in eine Klasse;
- Zeilen 50–56: Tests zeigen, dass die Klassen [Class04] und [Class05] auf dieselbe Weise funktionieren;
Ergebnisse
Die Ergebnisse in den Zeilen 3–10 zeigen, dass die Klassen [Class04] und [Class05] denselben Inhalt haben;
Fazit
Die Verwendung der Anweisung [use Trait] in einer Klasse entspricht dem Einfügen des Codes von [Trait] in die Klasse.
9.3. Verwendung desselben Traits in verschiedenen Klassen
Ein erster Vorteil des Traits scheint die Wiederverwendung desselben Codes (Attribute + Methoden) in verschiedenen Klassen zu sein. Wir werden jedoch sehen, dass man dasselbe Ziel auch mit einfachen Klassen erreichen kann.
Die gemeinsame Nutzung von trait durch mehrere Klassen wird durch das folgende Skript [trait-02.php] veranschaulicht:
<?php
trait Trait01 {
// Attribut
private $id = 0;
// Methode
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// Einbindung von Trait01
use Trait01 {
// Die Methode [Trait01::doSomething] ist
// in der Klasse unter dem Namen [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// klassenspezifische Methode
public function doSomething(): void {
// ID-Attribut
$this->id += 10;
// Verwendung der Methode „Trait01“
$this->doSomethingInTrait();
// lokale Anzeige
print "Class02->doSomething\n";
}
}
class Class03 {
// Einbindung von Trait01
use Trait01;
// klasseninterne Methode
public function doSomethingElse(): void {
// ID-Attribut
$this->id += 10;
// Verwendung der Methode von Trait01
$this->doSomething();
// lokale Anzeige
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 Merkmal, das ein Attribut (Zeile 5) und eine Methode (Zeilen 8–10) definiert.
- Der 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 dient der Behebung eines Konflikts: [Trait01] und [Class02] verfügen beide über eine Methode namens [doSomething]. Dabei 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 Interpreter PHP weiß nicht, welche Methode er aufrufen soll;
In Zeile 19 wird die Methode [Trait01::doSomething] in [doSomethingInTrait] umbenannt. Somit werden innerhalb von [Class02] folgende Notationen verwendet:
- [doSomethingInTrait], um die Methode [Trait01::doSomething] aufzurufen;
- [doSomething] zum Aufruf der Methode [Class02::doSomething];
- Zeilen 25, 27: Die Klasse [Class02] verwendet das Attribut und die Methode von [Trait01], als wären sie ihre eigenen;
- Zeilen 34–48: Die Klasse [Class03] ist identisch mit der Klasse [Class02]. Die Einbindung von [Trait01] ist hier einfacher, da es keine Kollision zwischen den Methoden von [Trait01] und [Class03] gibt;
Ergebnisse
Es ist zu beachten, dass das Merkmal [Trait01] nicht von den Klassen [Class02] und [Class03] gemeinsam genutzt wird. Somit wird dasAttribut [Trait01::i] durch die Einbeziehung von [Trait01] in die Klassen [Class02] und [Class03] zu zwei verschiedenen Attributen: [Class02::i] und [Class03::i]. Dies geht aus den Zeilen 2 und 4 der Ergebnisse hervor. Wäre das Attribut [Trait01::i] auf die Klassen [Class02] und [Class03] verteilt worden, hätte in Zeile 4 statt 10 der Wert 20 gestanden.
Das Skript [trait-03.php] zeigt, dass man das gleiche Ergebnis erzielen kann, indem man eine Klasse anstelle des Merkmals verwendet:
<?php
// Klasse, die das Merkmal ersetzt
class Class01 {
// Attribut
private $id = 0;
// Setter
public function setId(int $id) {
$this->id = $id;
}
// Getter
public function getId(): int {
return $this->id;
}
// Methode
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// Einbindung von Class01
private $class01;
// Setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// klassenspezifische Methode
public function doSomething(): void {
// Änderung eines Attributs von Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// Verwendung einer Methode von Class01
$this->class01->doSomething();
// lokale Anzeige
print "Class02->doSomething\n";
}
}
class Class03 {
// Einbindung von Class01
private $class01;
// Setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// lokale Methode der Klasse
public function doSomethingElse(): void {
// Änderung eines Attributs von Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// Verwendung einer Methode von Class01
$this->class01->doSomething();
// lokale Anzeige
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
// zwei Objekte
$class02 = new Class02();
$class03 = new Class03();
// greifen auf zwei verschiedene Instanzen von [Class01] zu
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// Überprüfung
$class02->doSomething();
$class03->doSomethingElse();
}
// test02 ----------------
function test02(): void {
// gemeinsam genutzte Instanz von [Class01]
$class01 = new Class01();
// zwei Objekte
$class02 = new Class02();
$class03 = new Class03();
// greifen auf dieselbe Instanz von [Class01] zu
$class02->setClass01($class01);
$class03->setClass01($class01);
// Überprüfung
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
// Test02
print "test02-----------------\n";
test02();
Anmerkungen
- Zeilen 4–23: Die Klasse [Class01] ersetzt den Strich [Trait01]. Der Code von [Trait01] war in den Code der Klassen [Class02] und [Class03] eingebunden. Hier wird dies nicht der Fall sein. Es handelt sich um einen Verweis auf den Klassencode [Class01], der in die Klassen [Class02] und [Class03] eingefügt wird. Das bedeutet, dass die Attribute der Klasse [Class01] außerhalb des Codes der Klassen [Class02] und [Class03] liegen. Da hier das Attribut [id] privat ist (Zeile 6), müssen ein Getter (Zeilen 14–16) und ein Setter (Zeilen 9–11) vorgesehen werden. Dies ist der erste Unterschied zum Merkmal: Es muss Code für den Zugriff auf die privaten Attribute der Klasse erstellt werden. Man hätte die Sichtbarkeit des Attributs [id] auf [public] ändern können, doch davon ist grundsätzlich abzuraten. Die Verwendung eines Setters zum Festlegen des Werts eines Attributs ermöglicht es, dessen Gültigkeit zu überprüfen;
- 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 von [Class01] müssen wir das Attribut [$this→class01] verwenden;
- Zeilen 48–69: Die Klasse [Class03] ist ein Klon der Klasse [Classe02], nur dass ihre Methode einen anderen Namen hat;
- Zeilen 72–82: Der erste Test. Dieser besteht darin, in die Klassen [Class02] und [Class03] zwei verschiedene Instanzen der Klasse [Class01] einzufügen (Zeilen 77 und 78);
- Zeilen 85–97: Der zweite Test fügt in die Klassen [Class02] und [Class03] dieselbe Instanz der Klasse [Class01] ein (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: Es werden dieselben Ergebnisse wie bei der Eigenschaft [Trait01] erzielt. Daraus lässt sich schließen, dass die Verwendung des Traits hier zwar nicht zwingend erforderlich ist, aber zu einer Reduzierung des Codes führt, 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 dieselbe Referenz von [Class01] in die Klassen [Class02] und [Class03] eingefügt wurde, wurde das Attribut [Class01::id] zwischen den beiden Klassen geteilt. Aus diesem Grund wird in Zeile 9 der Ergebnisse 20 statt 10 mit dem Trait angezeigt. Daraus lässt sich schließen: Wenn Attribute des Traits zwischen Klassen geteilt werden müssen, ist das Trait nicht verwendbar und es muss stattdessen eine Klasse verwendet werden;
9.4. Methoden in einem Trait zusammenfassen
Im vorherigen Beispiel enthielt der Trait Attribute und Methoden. Wir betrachten hier den Fall, in dem er nur Methoden enthält. In diesem Fall gleicht der Trait einer Faktorisierung von Methoden, die dann in verschiedenen Klassen verwendet werden können. Da der Trait hier keine Attribute hat, betrachten wir den Fall, in dem die darin zusammengefassten Methoden ausschließlich mit den ihnen übergebenen Parametern arbeiten. Tatsächlich ist dies nicht zwingend erforderlich: Ein Trait kann auf ein Attribut [$this→attribut1] zugreifen, ohne dieses Attribut selbst zu besitzen. Es ist dann Aufgabe der Klassen, die dieses Trait verwenden, das Attribut [$this→attribut1] bereitzustellen.
Für den Fall, dass das Trait nur Methoden enthält, die ausschließlich mit den übergebenen Parametern arbeiten, werden wir zeigen, dass das Trait dann durch eine Klasse ersetzt werden kann, die dieselben Methoden wie das Trait besitzt und diese als statisch deklariert.
Die Verwendung des Traits wird durch das Skript [trait-04.php] veranschaulicht, das das Trait aus dem vorherigen Beispiel übernimmt und dabei alle Attribute entfernt:
<?php
trait Trait01 {
// gemeinsam zu nutzende Methode
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// Einbindung von Trait01
use Trait01 {
// Die Methode [Trait01::doSomething] ist verfügbar
// in der Klasse unter dem Namen [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// Aufruf der Methode „Trait01“
$this->doSomethingInTrait();
// lokale Anzeige
print "Class02->doSomething\n";
}
}
class Class03 {
// Einbindung von Trait01
use Trait01;
// lokale Methode der Klasse
public function doSomethingElse(): void {
// Aufruf der Methode „Trait01“
$this->doSomething();
// lokale Anzeige
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 der Merkmal [Trait01] problemlos durch eine Klasse ersetzt werden. Dies wird durch das folgende Skript [trait-05.php] veranschaulicht:
<?php
abstract class Class01 {
// gemeinsam genutzte statische Methode
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// Methodenaufruf von Class01
Class01::doSomething();
// Lokale Anzeige
print "Class02->doSomething\n";
}
}
class Class03 {
// lokale Methode der Klasse
public function doSomethingElse(): void {
// Methodenaufruf von Class01
Class01::doSomething();
// Lokale Anzeige
print "Class03->doSomethingElse\n";
}
}
// Test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Kommentare
- Zeilen 3–10: Das Merkmal [Trait01] wird durch eine abstrakte Klasse [Class01] ersetzt, deren Methoden alle als statisch deklariert sind. Die Klasse wird nur deshalb als abstrakt deklariert, um ihre Instanziierung zu verhindern. Man hätte gerne auch [final] geschrieben, um eine Ableitung zu verhindern, aber PHP 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: Der gleiche Vorgang wird in [Class03] wiederholt;
Ergebnisse
Man erhält genau dasselbe Ergebnis wie mit dem Trait [Trait01], was zeigt, dass dessen Verwendung vermieden werden kann. Es wurde bereits erwähnt, dass die Methoden eines Traits auf ein Attribut [$this→attribut1] zugreifen können, ohne dass das Trait selbst über dieses Attribut verfügt. Es ist dann Aufgabe der Klassen, die dieses Trait verwenden, das Attribut [$this→attribut1] bereitzustellen. Dies ist ein Sonderfall: Man könnte das Attribut [$this→attribut1], über das die Klassen, die das Trait verwenden, verfügen müssen, genauso gut im Trait selbst „nach oben“ verlagern. Auf diese Weise wird es zwangsläufig Teil der Attribute der Klasse sein, die das Trait verwendet.
9.5. Mehrfachvererbung mit einem Trait
In der Fachliteratur ist häufig zu lesen, dass das Trait eine Mehrfachvererbung ermöglichen würde: die Möglichkeit für eine Klasse, von mehreren Klassen zu erben. Die Sprache C++ verfügt über diese Möglichkeit, nicht jedoch die Sprachen Java oder C#, die nur die einfache Vererbung kennen. Wir werden zeigen, dass die Verwendung eines Traits in einer abgeleiteten Klasse zwar die Implementierung von etwas ermöglicht, das einer Mehrfachvererbung ähnelt, dieser Anwendungsfall jedoch auch mit einfachen Klassen umgesetzt werden kann.
Das Skript [trait-06.php] verwendet eine abgeleitete Klasse und ein Merkmal:
<?php
trait Trait01 {
// Attribut
private $i;
// gemeinsam genutzte Methode
public function doSomethingInTrait01() {
// Änderung von Trait01::$i
$this->i++;
// Anzeige
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// Attribut
protected $j = 0;
// Methode
public function doSomethingInClass02(): void {
// Änderung Class02::j
$this->j += 10;
// Anzeige
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// abgeleitete Klasse
class Class03 extends Class02 {
// erbt von Class02:j und Trait01::i
// Einbindung von Trait01
use Trait01;
// Methode
public function doSomethingInClass03(): void {
// Verwendung der Methode von Trait01
$this->doSomethingInTrait01();
// Änderung von Trait01::i
$this->i += 100;
// Änderung von Class03::j (==Class02::j)
$this->j += 1000;
// Anzeige
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 Attribut bearbeitet;
- Zeilen 17–29: Eine Klasse [Class02], die nichts mit dem Trait [Trait01] zu tun hat. Sie nutzt dieses nicht;
- Zeile 19: Das einzige Attribut von [Class02] wurde mit der Sichtbarkeit [protected] deklariert, damit es in den abgeleiteten Klassen zugänglich ist;
- Zeile 32: Die Klasse [Class03] erweitert die Klasse [Class02]. Außerdem integriert sie das Merkmal [Trait01] (Zeile 35). Schließlich erbt sie die Attribute und Methoden von [Class02] und enthält die Attribute und Methoden von [Trait01]. Wir haben es also tatsächlich mit etwas zu tun, das der Mehrfachvererbung ähnelt;
Ergebnisse
Genau wie in einem vorherigen Beispiel werden wir zeigen, dass:
- der Trait durch eine Klasse ersetzt werden kann;
- anstatt das Trait in die abgeleitete Klasse einzubinden, wird die Referenz einer Instanz der Klasse eingebunden;
Das Skript [trait-07.php] lautet wie folgt:
<?php
class Class01 {
// Attribut
protected $i;
// Getter und Setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// Methode
public function doSomethingInClass01(): void {
// Änderung an Class01::$i
$this->i++;
// Anzeige
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// Attribut
protected $j = 0;
// klassenspezifische Methode
public function doSomethingInClass02(): void {
// Änderung Class02::j
$this->j += 10;
// Anzeige
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// Einbindung von Class01
private $class01;
// Setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// klasseninterne Methode
public function doSomethingInClass03(): void {
// Verwendung einer Methode von Class01
$this->class01->doSomethingInClass01();
// Änderung von Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// Änderung von Class03::j
$this->j += 1000;
// Anzeige
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 Merkmal [Trait01]. Da die Klasse [Class01] über eine Referenz in die Klassen eingebunden wird, wurden Get- und Set-Methoden für das Attribut [$i] vorgesehen;
- Zeilen 26–38: Die Klasse [Classe02] bleibt unverändert;
- Zeile 40: Die Klasse [Class03] erweitert die Klasse [Classe02];
- Zeile 42: Die Klasse [Classe01] wird über eine Referenz in [Classe03] eingebunden;
- Zeilen 45–47: Es wird eine Klasse setter vorgesehen, um die Referenz auf die Klasse [Classe01] zu initialisieren;
- Zeilen 50–61: Die Methode [doSomethingInClass03] führt dasselbe aus wie zuvor, allerdings mit komplexerem Code;
Ergebnisse
Aus diesem Beispiel lässt sich schließen, dass auch hier das Trait nicht unbedingt erforderlich ist, man muss jedoch anerkennen, dass es das Schreiben von kürzerem Code in der abgeleiteten Klasse ermöglicht.
9.6. Verwendung eines Traits anstelle einer abstrakten Klasse
Häufig trifft man auf folgenden Anwendungsfall: Man erstellt eine recht allgemeine Schnittstelle I, aus der mehrere Implementierungen hervorgehen können. Diese teilen sich gemeinsamen Code, unterscheiden sich jedoch in anderen Methoden. Dieser Anwendungsfall lässt sich auf zwei Arten umsetzen:
- Man erstellt eine abstrakte Klasse C, die den gemeinsamen Code der abgeleiteten Klassen zusammenfasst. Die Klasse C implementiert die Schnittstelle I, doch bestimmte Methoden, die in den abgeleiteten Klassen deklariert werden müssen, sind in der Klasse C als abstrakt deklariert, weshalb die Klasse C selbst abstrakt ist. Anschließend werden die von C abgeleiteten Klassen C1 und C2 erstellt, die jeweils auf ihre eigene Weise die nicht definierten (abstrakten) Methoden ihrer übergeordneten Klasse C implementieren;
- man erstellt ein Merkmal T, das nahezu identisch mit der abstrakten Klasse C aus der vorherigen Lösung ist. Dieses Merkmal implementiert die Schnittstelle I nicht, da es dies syntaktisch nicht kann. Anschließend werden die Klassen C1 und C2 erstellt, die die Schnittstelle I implementieren und das Trait T verwenden. Diese Klassen müssen nun nur noch die Methoden der Schnittstelle I implementieren, die nicht durch das von ihnen importierte Trait T implementiert werden;
Hier ist ein Beispiel, das die große Ähnlichkeit dieser beiden Lösungen verdeutlicht.
Anwendung 1 implementiert die zuvor beschriebene Lösung 1 [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// Attribute
protected $attr1 = 11;
protected $attr2 = 12;
// Getter und Setter
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;
}
// implementierte Methode
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// nicht implementierte Methode
abstract public function doSomethingElse(): void;
}
// abgeleitete Klasse 1
class Class1 extends AbstractClass {
// Attribut
private $attr3 = 13;
// Getter und Setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// Implementierung doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// abgeleitete Klasse 2
class Class2 extends AbstractClass {
// Attribut
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// Implementierung doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// externe Funktion
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 entsprechenden Gettern und Settern (Zeilen 12–32), implementiert die Methode [doSomething] der Schnittstelle [Interface1] (Zeilen 35–37) kann jedoch die Methode [doSomethingElse] nicht implementieren. Diese wird daher als abstrakt deklariert (Zeile 40). Die abstrakte Klasse [AbstractClass] kann nicht instanziiert werden und muss zwingend abgeleitet werden, um einen Zweck zu erfüllen;
- Zeilen 44–63: Die Klasse „Class1“ erweitert die abstrakte Klasse „[AbstractClass]“ und implementiert somit die Schnittstelle „[Interface1]“ (Zeile 14). Sie liefert einen Methodenkörper für die Methode [doSomethingElse], die ihre übergeordnete Klasse nicht definiert hatte (Zeilen 59–61). Außerdem fügt sie den Attributen ihrer übergeordneten Klasse ein weiteres Attribut hinzu (Zeilen 46–56);
- Zeilen 66–82: Die Klasse „Class2“ erweitert die abstrakte Klasse „[AbstractClass]“ und implementiert somit die Schnittstelle „[Interface1]“ (Zeile 66). Sie füllt die Methode [doSomethingElse] mit einem Methodenkörper aus, den ihre übergeordnete Klasse nicht definiert hatte (Zeilen 80–82). Außerdem fügt sie den Attributen ihrer übergeordneten Klasse ein weiteres Attribut hinzu (Zeilen 68–77);
- Zeilen 87–90: Die Funktion [useInterfaceWith] erhält als Parameter einen Typ [Interface1] 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. Das ist korrekt, da diese beiden 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 mit dem Skript [trait-09.php]. Dabei wird die abstrakte Klasse durch ein Trait ersetzt:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// Attribute
private $attr1 = 11;
private $attr2 = 12;
// Getter und Setter
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;
}
// implementierte Methode
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// abgeleitete Klasse 1
class Class1 implements Interface1 {
// Verwendung des Traits
use Trait1;
// Attribut
private $attr3 = 13;
// Getter und Setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// Implementierung doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// abgeleitete Klasse 2
class Class2 implements Interface1 {
// Verwendung des Merkmals
use Trait1;
// Attribut
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// Implementierung doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// Externe Funktion unter Verwendung der Schnittstelle
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 Merkmal [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 dienen dazu, abgeleiteten Klassen direkten Zugriff auf die Attribute der übergeordneten Klasse (protected) oder des Traits (private) zu gewähren, ohne den Umweg über Getter und Setter nehmen zu müssen;
- das Merkmal [Trait1] deklariert die abstrakte Methode [doSomethingElse] nicht;
- Zeilen 41–62: Die Klasse „[Class1]“ aus Lösung 2 ist bis auf die folgenden Details identisch mit der Klasse „[Class1]“ aus Lösung 1:
- 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 Merkmal [Trait1], um einen Teil der Schnittstelle zu implementieren;
- Zeilen 65–85: Hier gelten dieselben Anmerkungen wie für [Class1];
- Zeilen 87–95: Der restliche Code bleibt unverändert;
Ergebnisse
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Man erhält tatsächlich die gleichen Ergebnisse.
9.7. Fazit
Aus den vorangegangenen Beispielen geht hervor, dass die Anwendungsfälle, in denen die Verwendung des Strichs einen klaren Vorteil bringen würde, nicht eindeutig sind. In unseren Beispielen kann man immer darauf verzichten, indem man ihn durch eine Klasse ersetzt. Es scheint jedoch, dass seine Verwendung praktisch ist, um Code zwischen verschiedenen abgeleiteten Klassen zu faktorisieren, so als ob dieser Code zu einer übergeordneten Klasse gehören würde. Genau das werden wir in einem folgenden Beispiel tun.