Öffentliche Bekanntmachung:
Ich möchte zu Protokoll geben, dass ich der Meinung bin, dass Traits fast immer nach Code riechen und zu Gunsten von Kompositionen vermieden werden sollten. Ich bin der Meinung, dass Einfachvererbung häufig bis zu dem Punkt missbraucht wird, an dem sie ein Anti-Pattern ist, und Mehrfachvererbung verstärkt dieses Problem nur noch. In den meisten Fällen sind Sie besser bedient, wenn Sie die Komposition der Vererbung vorziehen (egal ob Einzel- oder Mehrfachvererbung). Wenn Sie immer noch an Traits und ihrer Beziehung zu Schnittstellen interessiert sind, lesen Sie weiter ...
Lassen Sie uns mit folgendem beginnen:
Die objektorientierte Programmierung (OOP) kann eine Nur weil Sie Klassen verwenden, heißt das nicht, dass Ihr Code objektorientiert (OO).
Um OO-Code zu schreiben, müssen Sie verstehen, dass es bei OOP eigentlich um die Fähigkeiten Ihrer Objekte geht. Sie müssen über Klassen im Hinblick darauf nachdenken, was sie kann tun anstelle dessen, was sie tatsächlich tun . Dies steht im krassen Gegensatz zur traditionellen prozeduralen Programmierung, bei der der Schwerpunkt darauf liegt, dass ein Stück Code "etwas tut".
Wenn es bei OOP-Code um Planung und Entwurf geht, ist eine Schnittstelle der Bauplan und ein Objekt das fertig gebaute Haus. In der Zwischenzeit sind Traits einfach ein Mittel, um das Haus zu bauen, das durch den Bauplan (die Schnittstelle) festgelegt ist.
Schnittstellen
Warum sollten wir also Schnittstellen verwenden? Ganz einfach: Schnittstellen machen unseren Code weniger spröde. Wenn Sie diese Aussage anzweifeln, fragen Sie jeden, der schon einmal gezwungen war, Legacy-Code zu pflegen, der nicht mit Schnittstellen geschrieben wurde.
Die Schnittstelle ist ein Vertrag zwischen dem Programmierer und seinem Code. Die Schnittstelle sagt: "Solange du dich an meine Regeln hältst, kannst du mich implementieren, wie du willst, und ich verspreche, dass ich deinen anderen Code nicht kaputt mache."
Nehmen wir als Beispiel ein reales Szenario (keine Autos oder Widgets):
Sie möchten ein Caching-System für ein Web die Serverlast zu verringern
Sie beginnen mit dem Schreiben einer Klasse zum Zwischenspeichern von Anfrageantworten mit APC:
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
Dann prüfen Sie in Ihrem HTTP-Antwortobjekt, ob ein Cache-Treffer vorliegt, bevor Sie die eigentliche Antwort erzeugen:
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
Dieser Ansatz funktioniert hervorragend. Aber vielleicht entscheiden Sie sich ein paar Wochen später dafür, ein dateibasiertes Cache-System anstelle von APC zu verwenden. Jetzt müssen Sie den Code Ihres Controllers ändern, weil Sie Ihren Controller so programmiert haben, dass er mit der Funktionalität des ApcCacher
Klasse und nicht auf eine Schnittstelle, die die Fähigkeiten der ApcCacher
Klasse. Nehmen wir an, Sie hätten statt der oben genannten die Controller
Klasse, die auf eine CacherInterface
anstelle der konkreten ApcCacher
etwa so:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Dazu definieren Sie Ihre Schnittstelle wie folgt:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
Im Gegenzug haben Sie sowohl Ihre ApcCacher
und Ihre neue FileCacher
Klassen implementieren die CacherInterface
und Sie programmieren Ihre Controller
Klasse, um die von der Schnittstelle geforderten Fähigkeiten zu nutzen.
Dieses Beispiel zeigt (hoffentlich), wie die Programmierung für eine Schnittstelle es Ihnen ermöglicht, die interne Implementierung Ihrer Klassen zu ändern, ohne sich Gedanken darüber zu machen, ob die Änderungen Ihren anderen Code zerstören.
Eigenschaften
Traits hingegen sind einfach eine Methode zur Wiederverwendung von Code. Schnittstellen sollten nicht als eine sich gegenseitig ausschließende Alternative zu Traits betrachtet werden. In der Tat, die Erstellung von Merkmalen, die die von einer Schnittstelle geforderten Fähigkeiten erfüllen, ist der ideale Anwendungsfall .
Sie sollten Traits nur verwenden, wenn mehrere Klassen die gleiche Funktionalität teilen (wahrscheinlich durch die gleiche Schnittstelle vorgegeben). Es macht keinen Sinn, einen Trait zu verwenden, um Funktionalität für eine einzelne Klasse bereitzustellen: Das verschleiert nur, was die Klasse tut, und ein besseres Design würde die Funktionalität des Traits in die entsprechende Klasse verschieben.
Betrachten Sie die folgende Implementierung einer Eigenschaft:
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
Ein konkreteres Beispiel: Stellen Sie sich vor, Ihre beiden FileCacher
und Ihr ApcCacher
aus der Schnittstellendiskussion verwenden dieselbe Methode, um festzustellen, ob ein Cache-Eintrag veraltet ist und gelöscht werden sollte (natürlich ist dies im wirklichen Leben nicht der Fall, aber machen Sie sich nichts vor). Man könnte einen Trait schreiben und beiden Klassen erlauben, ihn für die gemeinsame Schnittstellenanforderung zu verwenden.
Ein letztes Wort der Vorsicht: Achten Sie darauf, dass Sie es mit den Merkmalen nicht übertreiben. Oft werden Traits als Krücke für schlechtes Design verwendet, wenn eindeutige Klassenimplementierungen ausreichen würden. Sie sollten Traits auf die Erfüllung von Schnittstellenanforderungen beschränken, um den besten Code zu entwerfen.