394 Stimmen

Eigenschaften vs. Schnittstellen

Ich habe in letzter Zeit versucht, mich in PHP einzuarbeiten, und bin immer wieder auf Traits gestoßen. Ich verstehe das Konzept der horizontalen Wiederverwendung von Code und dass man nicht unbedingt von einer abstrakten Klasse erben will. Was ich nicht verstehe, ist: Was ist der entscheidende Unterschied zwischen der Verwendung von Traits und Schnittstellen?

Ich habe versucht, einen vernünftigen Blogbeitrag oder Artikel zu finden, in dem erklärt wird, wann man das eine oder das andere verwenden sollte, aber die Beispiele, die ich bisher gefunden habe, scheinen so ähnlich zu sein, dass sie identisch sind.

601voto

rdlowrey Punkte 38783

Ö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.

260voto

Alec Gorge Punkte 16572

Eine Schnittstelle definiert eine Reihe von Methoden, die die implementierende Klasse debe Gerät.

Wenn ein Merkmal use Die Implementierungen der Methoden sind ebenfalls vorhanden, was in einem solchen Programm nicht der Fall ist. Interface .

Das ist der größte Unterschied.

Von der Horizontale Wiederverwendung für PHP RFC :

Traits sind ein Mechanismus zur Wiederverwendung von Code in Sprachen mit einfacher Vererbung wie PHP. Ein Trait soll einige Einschränkungen der Einfachvererbung verringern, indem er es einem Entwickler ermöglicht, Methodensätze in mehreren unabhängigen Klassen, die in verschiedenen Klassenhierarchien leben, frei wiederzuverwenden.

76voto

Troy Alford Punkte 25723

A trait ist im Wesentlichen PHPs Implementierung eines mixin und ist im Grunde ein Satz von Erweiterungsmethoden, die jeder Klasse durch Hinzufügen der Option trait . Die Methoden werden dann Teil der Implementierung dieser Klasse, aber ohne Verwendung von Vererbung .

Von der PHP-Handbuch (Hervorhebung von mir):

Merkmale sind ein Mechanismus für Code-Wiederverwendung in Einzelvererbungssprachen wie PHP. ... Sie ist eine Ergänzung zur traditionellen Vererbung und ermöglicht die horizontale Komposition von Verhalten, d.h. die Anwendung von Klassenmitgliedern, ohne dass Vererbung erforderlich ist.

Ein Beispiel:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Mit der oben definierten Eigenschaft kann ich nun Folgendes tun:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

Wenn ich zu diesem Zeitpunkt eine Instanz der Klasse MyClass hat sie zwei Methoden, genannt foo() y bar() - die stammen aus myTrait . Und - beachten Sie, dass die trait -definierten Methoden haben bereits einen Methodenrumpf - den ein Interface -definierte Methode kann das nicht.

Außerdem verwendet PHP, wie viele andere Sprachen auch, eine Einzelvererbungsmodell - Das bedeutet, dass eine Klasse von mehreren Schnittstellen, aber nicht von mehreren Klassen abgeleitet werden kann. Allerdings kann eine PHP-Klasse kann haben mehrere trait Einschlüsse - was es dem Programmierer ermöglicht, wiederverwendbare Teile einzuschließen - wie bei der Einbindung mehrerer Basisklassen.

Ein paar Dinge sind zu beachten:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Polymorphismus:

In dem früheren Beispiel, in dem MyClass erweitert SomeBaseClass , MyClass ist eine Instanz von SomeBaseClass . Mit anderen Worten: Ein Array wie SomeBaseClass[] bases kann Instanzen enthalten von MyClass . Ähnlich ist es, wenn MyClass erweitert IBaseInterface , eine Reihe von IBaseInterface[] bases könnte Instanzen enthalten von MyClass . Es gibt kein solches polymorphes Konstrukt mit einer trait - weil ein trait ist im Wesentlichen nur Code, der zur Bequemlichkeit des Programmierers in jede Klasse kopiert wird, die ihn verwendet.

Vorrangig:

Wie im Handbuch beschrieben:

Ein geerbtes Mitglied einer Basisklasse wird durch ein Mitglied überschrieben, das durch einen Trait eingefügt wurde. Die Rangfolge ist so, dass die Mitglieder der aktuellen Klasse die Methoden des Traits überschreiben, die ihrerseits die geerbten Methoden überschreiben.

Stellen Sie sich also das folgende Szenario vor:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

Beim Erstellen einer Instanz von MyClass (siehe oben) geschieht Folgendes:

  1. En Interface IBase erfordert eine parameterlose Funktion namens SomeMethod() zur Verfügung gestellt werden.
  2. Die Basisklasse BaseClass stellt eine Implementierung dieser Methode zur Verfügung, die dem Bedarf gerecht wird.
  3. En trait myTrait bietet eine parameterlose Funktion namens SomeMethod() auch, die Vorrang hat über die BaseClass -Version
  4. En class MyClass bietet seine eigene Version von SomeMethod() - die Vorrang hat über die trait -Version.

Schlussfolgerung

  1. Eine Interface kann keine Standardimplementierung eines Methodenkörpers bereitstellen, während eine trait kann.
  2. Eine Interface ist eine polymorph , vererbt Konstrukt - während ein trait ist nicht.
  3. Mehrere Interface s können in der gleichen Klasse verwendet werden, und ebenso können mehrere trait s.

32voto

J. Bruni Punkte 19752

Je pense traits sind nützlich, um Klassen zu erstellen, die Methoden enthalten, die als Methoden von mehreren verschiedenen Klassen verwendet werden können.

Zum Beispiel:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Sie können diese "Fehler"-Methode in jeder Klasse haben und verwenden, die verwendet diese Eigenschaft.

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

Während mit interfaces können Sie nur die Signatur der Methode deklarieren, aber nicht den Code ihrer Funktionen. Außerdem müssen Sie, um eine Schnittstelle zu verwenden, einer Hierarchie folgen, indem Sie implements . Dies ist bei Merkmalen nicht der Fall.

Es ist völlig anders!

28voto

Supun Praneeth Punkte 2718

Für Anfänger könnte die obige Antwort schwierig sein, aber so ist sie am einfachsten zu verstehen:

Eigenschaften

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

Wenn Sie also eine sayHello Funktion in anderen Klassen zu verwenden, ohne die gesamte Funktion neu zu erstellen, können Sie Traits verwenden,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Cooles Recht!

Sie können nicht nur Funktionen, sondern alles in einem Trait (Funktion, Variablen, const...) verwenden. Außerdem können Sie mehrere Traits verwenden: use SayWorld, AnotherTraits;

Schnittstelle

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

Darin unterscheiden sich also Schnittstellen von Merkmalen: Sie müssen alles, was in der Schnittstelle enthalten ist, in einer implementierten Klasse neu erstellen. Schnittstellen haben keine Implementierung und Schnittstellen können nur Funktionen und Konstanten haben, aber keine Variablen.

Ich hoffe, das hilft!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X