Mitteilung des öffentlichen Dienstes:
Ich möchte festhalten, dass ich glaube, dass Merkmale fast immer ein Codegeruch sind und zugunsten der Komposition vermieden werden sollten. Ich bin der Meinung, dass eine einzelne Vererbung häufig bis zu einem Anti-Muster missbraucht wird und eine mehrfache Vererbung dieses Problem nur noch verstärkt. In den meisten Fällen werden Sie viel besser bedient, wenn Sie die Komposition der Vererbung vorziehen (sei es einzeln oder mehrfach). Wenn Sie immer noch an Merkmalen und deren Beziehung zu Schnittstellen interessiert sind, lesen Sie weiter ...
Beginnen wir damit:
Objektorientierte Programmierung (OOP) kann ein schwer zu fassendes Paradigma sein. Nur weil Sie Klassen verwenden, bedeutet dies nicht, dass Ihr Code objektorientiert (OO) ist.
Um OO-Code zu schreiben, müssen Sie verstehen, dass es bei OOP wirklich um die Funktionen Ihrer Objekte geht. Sie müssen über Klassen nachdenken, was sie können, anstatt was sie tatsächlich tun . Dies steht in krassem Gegensatz zur traditionellen prozeduralen Programmierung, bei der der Schwerpunkt darauf liegt, ein bisschen Code "etwas tun" zu lassen.
Wenn es beim OOP-Code um Planung und Design geht, ist eine Schnittstelle die Blaupause und ein Objekt das vollständig konstruierte Haus. In der Zwischenzeit sind Merkmale lediglich eine Möglichkeit, das durch die Blaupause (die Schnittstelle) festgelegte Haus zu bauen.
Schnittstellen
Warum sollten wir also Schnittstellen verwenden? Schnittstellen machen unseren Code ganz einfach weniger spröde. Wenn Sie an dieser Aussage zweifeln, fragen Sie jeden, der gezwungen war, Legacy-Code zu pflegen, der nicht für Schnittstellen geschrieben wurde.
Die Schnittstelle ist ein Vertrag zwischen dem Programmierer und seinem Code. Die Benutzeroberfläche sagt: "Solange Sie sich an meine Regeln halten, können Sie mich implementieren, wie Sie möchten, und ich verspreche, dass ich Ihren anderen Code nicht brechen werde."
Stellen Sie sich als Beispiel ein reales Szenario vor (keine Autos oder Widgets):
Sie möchten ein Caching-System für eine Webanwendung implementieren, um die Serverlast zu verringern
Sie schreiben zunächst eine Klasse, um Anforderungsantworten mit APC zwischenzuspeichern:
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);
}
}
Anschließend suchen Sie in Ihrem HTTP-Antwortobjekt nach einem Cache-Treffer, bevor Sie die gesamte Arbeit ausführen, um die tatsächliche Antwort zu generieren:
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 ein paar Wochen später entscheiden Sie sich, ein dateibasiertes Cache-System anstelle von APC zu verwenden. Jetzt müssen Sie Ihren Controller-Code ändern, da Sie Ihren Controller so programmiert haben, dass er mit der Funktionalität der ApcCacher
Klasse und nicht mit einer Schnittstelle arbeitet, die die Funktionen der ApcCacher
Klasse ausdrückt . Nehmen wir an, Sie hätten die Controller
Klasse CacherInterface
stattdessen auf a anstatt auf Beton gesetzt ApcCacher
:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Dazu definieren Sie Ihre Benutzeroberfläche folgendermaßen:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
Im Gegenzug müssen sowohl Ihre ApcCacher
als auch Ihre neuen FileCacher
Klassen die implementieren CacherInterface
und Ihre Controller
Klasse so programmieren , dass sie die für die Schnittstelle erforderlichen Funktionen nutzt.
Dieses Beispiel zeigt (hoffentlich), wie Sie durch Programmieren auf eine Schnittstelle die interne Implementierung Ihrer Klassen ändern können, ohne sich Sorgen machen zu müssen, ob die Änderungen Ihren anderen Code beschädigen.
Züge
Merkmale hingegen sind lediglich eine Methode zur Wiederverwendung von Code. Schnittstellen sollten nicht als sich gegenseitig ausschließende Alternative zu Merkmalen betrachtet werden. In der Tat ist das Erstellen von Merkmalen, die die für eine Schnittstelle erforderlichen Funktionen erfüllen, der ideale Anwendungsfall .
Sie sollten Merkmale nur verwenden, wenn mehrere Klassen dieselbe Funktionalität verwenden (wahrscheinlich von derselben Schnittstelle vorgegeben). Es macht keinen Sinn, ein Merkmal zu verwenden, um Funktionalität für eine einzelne Klasse bereitzustellen: Dies verschleiert nur, was die Klasse tut, und ein besseres Design würde die Funktionalität des Merkmals in die relevante Klasse verschieben.
Betrachten Sie die folgende Implementierung von Merkmalen:
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, Sie FileCacher
und Ihre ApcCacher
Mitarbeiter aus der Schnittstellendiskussion verwenden dieselbe Methode, um festzustellen, ob ein Cache-Eintrag veraltet ist und gelöscht werden sollte (dies ist im wirklichen Leben offensichtlich nicht der Fall, aber machen Sie mit). Sie können ein Merkmal schreiben und beiden Klassen erlauben, es für die allgemeine Schnittstellenanforderung zu verwenden.
Ein letztes Wort zur Vorsicht: Achten Sie darauf, dass Sie nicht mit Merkmalen über Bord gehen. Oft werden Merkmale als Krücke für schlechtes Design verwendet, wenn eindeutige Klassenimplementierungen ausreichen würden. Sie sollten Merkmale darauf beschränken, die Schnittstellenanforderungen für das beste Code-Design zu erfüllen.