Warum kann PHP Trait keine Schnittstellen implementieren?

81

Ich frage mich, warum PHP Trait (PHP 5.4) keine Schnittstellen implementieren kann.

Für ein Update von der Antwort von user1460043 => ... kann keine Klasse erforderlich sein, die es zum Implementieren einer bestimmten Schnittstelle verwendet

Ich verstehe, dass es offensichtlich sein könnte, weil die Leute denken könnten, wenn a Class Aein verwendet, Trait Tdas ein implementiert interface I, dann Class Asollte das das interface Iungerichtete implementieren (und dies ist nicht wahr, weil Class Aes Merkmalsmethoden umbenennen könnte).

In meinem Fall ruft mein Merkmal Methoden von der Schnittstelle auf, die die Klasse, die das Merkmal verwendet, implementiert.

Das Merkmal ist in der Tat eine Implementierung einiger Methoden der Schnittstelle. Also möchte ich im Code "entwerfen", dass jede Klasse, die mein Merkmal verwenden möchte, die Schnittstelle implementieren muss. Dies würde es dem Merkmal ermöglichen, von der Schnittstelle definierte Klassenmethoden zu verwenden und sicherzustellen, dass sie in der Klasse vorhanden sind.

Leto
quelle
12
Das ist nicht der Punkt, ich kenne den Unterschied zwischen Eigenschaften und Schnittstellen.
Leto
1
Vielleicht gibt es einen technischen Grund, aber ich frage mich, warum Sie wollen? Sie können ein Merkmal nicht instanziieren, sodass die Implementierung einer Schnittstelle keine Vorteile beim Schreiben von Texten bietet. Wenn Sie möchten, dass Klassen, die das Merkmal zum Implementieren einer Schnittstelle verwenden, erzwungen werden, fragen Sie sich, ob eine (abstrakte) Basisklasse besser geeignet wäre.
Sie haben Recht, ich könnte überall abstrakte Klassen verwenden, aber ich aktualisiere meinen Code auf Trait, und es vermeidet Probleme, die ich mit der einfachen Vererbung hatte. Deshalb verwende ich Trait. Vielleicht ist es in diesem Fall möglich, in einigen anderen jedoch nicht.
Leto
2
Oder einfacher ausgedrückt: Warum sind Traits in PHP nicht typisiert?
Nnevala

Antworten:

97

Die wirklich kurze Version ist einfacher, weil Sie nicht können. So funktionieren Eigenschaften nicht.

Wenn Sie use SomeTrait;in PHP schreiben , weisen Sie den Compiler (effektiv) an, den Code aus dem Merkmal zu kopieren und in die Klasse einzufügen, in der er verwendet wird.

Da sich das use SomeTrait;innerhalb der Klasse befindet, kann es nicht implements SomeInterfacezur Klasse hinzugefügt werden , da dies außerhalb der Klasse liegen muss.

"Warum sind in PHP keine Traits-Typen?"

Weil sie nicht instanziiert werden können. Merkmale sind eigentlich nur ein Sprachkonstrukt (das den Compiler anweist, den Merkmalcode zu kopieren und in diese Klasse einzufügen), im Gegensatz zu einem Objekt oder Typ, auf das Ihr Code verweisen kann.

Also möchte ich im Code "entwerfen", dass jede Klasse, die mein Merkmal verwenden möchte, die Schnittstelle implementieren muss.

Dies kann erzwungen werden, indem eine abstrakte Klasse für usedas Merkmal verwendet und dann Klassen daraus erweitert werden.

interface SomeInterface{
    public function someInterfaceFunction();
}

trait SomeTrait {
    function sayHello(){
        echo "Hello my secret is ".static::$secret;
    }
}

abstract class AbstractClass implements SomeInterface{
    use SomeTrait;
}

class TestClass extends AbstractClass {
    static public  $secret = 12345;

    //function someInterfaceFunction(){
        //Trying to instantiate this class without this function uncommented will throw an error
        //Fatal error: Class TestClass contains 1 abstract method and must therefore be 
        //declared abstract or implement the remaining methods (SomeInterface::doSomething)
    //}
}

$test = new TestClass();

$test->sayHello();

Wenn Sie jedoch durchsetzen müssen, dass eine Klasse, die ein Merkmal verwendet, eine bestimmte Methode hat, verwenden Sie möglicherweise Merkmale, bei denen Sie eigentlich abstrakte Klassen sein sollten.

Oder dass Sie Ihre Logik falsch herum haben. Sie sollten Klassen benötigen, die Schnittstellen implementieren, die bestimmte Funktionen haben, und nicht, dass sie sich selbst als Implementierung einer Schnittstelle deklarieren müssen, wenn sie bestimmte Funktionen haben.

Bearbeiten

Tatsächlich können Sie abstrakte Funktionen in Traits definieren, um eine Klasse zur Implementierung der Methode zu zwingen. z.B

trait LoggerTrait {

    public function debug($message, array $context = array()) {
        $this->log('debug', $message, $context);
    }

    abstract public function log($level, $message, array $context = array());
}

Dies erlaubt Ihnen jedoch immer noch nicht, die Schnittstelle in das Merkmal zu implementieren, und riecht immer noch nach einem schlechten Design, da Schnittstellen viel besser sind als Merkmale bei der Definition eines Vertrags, den eine Klasse erfüllen muss.

Danack
quelle
2
Wie würden Sie vorschlagen, dies dann darzulegen? Ich habe eine Human-Klasse. Diese Klasse wird basierend auf Job in Unterklassen abstrahiert, aber viele dieser Jobs haben Funktionen gemeinsam, die am besten mit gemeinsam genutztem Code implementiert werden können (zum Beispiel benötigen sowohl eine Sekretärin als auch ein Programmierer die typeMethode ). Können Sie sich vorstellen, wie dies ohne Merkmale umgesetzt werden könnte?
Scragar
@scragar sollten Sie das bei programmers.stackexchange.com erfragen, aber die Kurzversion ist, dass ich 'Human' mit mehreren 'Jobs' zusammensetzen würde, um eine 'WorkingHuman'-Klasse zu sein.
Danack
1
Einer noch. Wenn die Schnittstelle einen Sensibilisierungsvertrag definiert und dieser Vertrag für die meisten Implementierungen üblich ist. Diese Implementierungen haben jedoch ihren eigenen Typ drei. So etwas wie Befehl mit ContainerAwareInterface. Comand hat jedoch seine eigenen spezifischen Anwendungsbereiche. Ich muss mich also jedes Mal wiederholen, wenn ich Container Awareness benötige, aber wenn ich Trait verwende, kann ich keinen eigenen Vertrag für eine bestimmte Schnittstelle definieren. Vielleicht sollten Kernentwickler Go-Type-Schnittstellen in Betracht ziehen (z. B. Structural Typing)?
Lazycommit
3
Es ist wirklich seltsam, da meine Kollegen und ich eigentlich nur Merkmale verwenden, wenn wir Code gemeinsam nutzen möchten, der in mehreren Klassen erforderlich ist, die eine Schnittstelle implementieren, aber von verschiedenen Vorfahren stammen. Es gibt auch keine vernünftige Erklärung, warum der Compiler den Code innerhalb der Klasse ändern kann, nicht jedoch die von dieser Klasse implementierten Schnittstellen. ... es ist einfach eine "fehlende" Funktion ... "weil du nicht kannst" erklärt dies am besten
Summer-Sky
5
Ich glaube, dass PHP-Kernentwickler ein bisschen Scala studieren sollten, wo Merkmale als vollwertige Typen gelten ... Ich finde es traurig, dass PHP sein Typisierungssystem schrittweise verbessern möchte, aber vorhandene gut funktionierende Implementierungen nicht berücksichtigt
Vincent Pazeller
26

Es gibt einen RFC: Merkmale mit Schnittstellen schlagen vor, der Sprache Folgendes hinzuzufügen:

trait SearchItem implements SearchItemInterface
{
    ...
}

Für die Schnittstelle erforderliche Methoden können entweder vom Merkmal implementiert oder als abstrakt deklariert werden. In diesem Fall wird erwartet, dass die Klasse, die das Merkmal verwendet, es implementiert.

Diese Funktion wird derzeit von der Sprache nicht unterstützt, wird jedoch derzeit geprüft (aktueller Status des RFC lautet: Unter Diskussion ).

Ilija
quelle
Ich nehme an, wenn dies bestätigt wird, möchten die Leute, dass immer mehr Funktionen aus normalen Klassen in Eigenschaften implementiert werden. Bis es keinen Unterschied zwischen ihnen gibt und wir eine Art Frankenstein-Merkmal haben, das Bedenken und Verantwortlichkeiten nicht richtig aufteilt. Wie die beste Antwort unterstreicht, sind Merkmale als Bequemlichkeit beim Kopieren zu betrachten. sollte Klassengrenzen nicht zu sehr überschreiten. Wir möchten, dass eine Klasse eine Schnittstelle implementiert, unabhängig davon, ob die Implementierung aus direktem Code oder aus der Verwendung eines Merkmals stammt. Das Implementieren von Schnittstellen in Eigenschaften kann verwirrend und irreführend sein
Kamafeather
Eine großartige Anwendung von Merkmalen ist die Bereitstellung einer einfach einzufügenden Standardimplementierung einer Schnittstelle. Wenn Sie sicherstellen möchten, dass ein Merkmal eine Schnittstelle erfüllt, wäre es schön, wenn die Compiler helfen
The Mighty Chris
Dieser Vorschlag sieht nicht vor , dass eine Klasse, die ein Merkmal verwendet, diese Merkmalsschnittstellen automatisch implementiert (dies würde nicht funktionieren, da Sie Merkmalsmethoden umbenennen / ersetzen können). Das schlüpfrige Argument, dass das Bestehen dieses
The Mighty Chris
9

[...] um im Code zu "entwerfen", dass jede Klasse, die mein Merkmal verwenden möchte, die Schnittstelle implementieren muss. Dies würde es dem Merkmal ermöglichen, von der Schnittstelle definierte Klassenmethoden zu verwenden und sicherzustellen, dass sie in der Klasse vorhanden sind.

Das klingt sehr vernünftig und ich würde nicht sagen, dass irgendetwas mit Ihrem Design nicht stimmt. Mit dieser Idee wurden Merkmale vorgeschlagen, siehe den zweiten Punkt hier:

  • Ein Merkmal bietet eine Reihe von Methoden, die das Verhalten implementieren.
  • Ein Merkmal erfordert eine Reihe von Methoden, die als Parameter für das bereitgestellte Verhalten dienen.
  • [...]

Schärli et al., Traits: Composable Units of Behavior, ECOOP'2003, LNCS 2743, S. 248–274, Springer Verlag, 2003, Seite 2

Daher wäre es vielleicht angemessener zu sagen, dass für ein Merkmal eine Schnittstelle erforderlich sein soll , und nicht, um es zu "implementieren".

Ich sehe keinen Grund, warum es unmöglich sein sollte, diese Funktion "Merkmal erfordert (seine Verbraucherklassen müssen implementiert werden) eine Schnittstelle" in PHP zu haben, aber derzeit scheint es zu fehlen.

Wie @Danack in seiner Antwort feststellt , können Sie abstrakte Funktionen im Merkmal verwenden, um sie von Klassen zu "fordern", die das Merkmal verwenden. Leider können Sie dies nicht mit privaten Funktionen tun .

user1460043
quelle
1

Ich stimme der Antwort von @Danack zu, werde sie jedoch ein wenig ergänzen.

Die wirklich kurze Version ist einfacher, weil Sie nicht können. So funktionieren Eigenschaften nicht.

Ich kann mir nur wenige Fälle vorstellen, in denen das, was Sie anfordern, notwendig ist und als Designproblem offensichtlicher ist als als Sprachfehler. Stellen Sie sich vor, es gibt eine Schnittstelle wie diese:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

Es wurde ein Merkmal erstellt, das eine der in der Schnittstelle definierten Funktionen implementiert, dabei jedoch andere Funktionen verwendet, die ebenfalls von der Schnittstelle definiert werden . Dies führt zu dem Fehler, dass, wenn die Klasse, die das Feature verwendet, die Schnittstelle nicht implementiert , alles nicht abgerufen werden kann auslösen

trait Triggerable
{
    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

class Warrior
{
    use Triggerable;
}

Eine einfache Lösung besteht darin, die Klasse, die das Merkmal verwendet, zu zwingen, auch diese Funktionen zu implementieren:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

Das Merkmal ist also nicht vollständig von der Schnittstelle abhängig , sondern ein Vorschlag zur Implementierung einer seiner Funktionen, da die Klasse bei Verwendung des Merkmals die Implementierung der abstrakten Methoden verlangt.

Endgültiges Design

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}


class Warrior implements Weaponize
{
    use Triggerable;

    public function hasAmmunition()
    {
        // TODO: Implement hasAmmunition() method.
    }

    public function fire()
    {
        // TODO: Implement fire() method.
    }

    public function recharge()
    {
        // TODO: Implement recharge() method.
    }
}

Bitte entschuldigen Sie mein Englisch

NekoOs
quelle