Mehrfachvererbung in PHP

97

Ich suche nach einem guten, sauberen Weg, um die Tatsache zu umgehen, dass PHP5 immer noch keine Mehrfachvererbung unterstützt. Hier ist die Klassenhierarchie:

Nachricht
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Die beiden Arten von Einladungsklassen haben viel gemeinsam. Ich würde gerne eine gemeinsame Elternklasse haben, Einladung, von der beide erben würden. Leider haben sie auch viel mit ihren derzeitigen Vorfahren gemeinsam ... TextMessage und EmailMessage. Klassischer Wunsch nach Mehrfachvererbung hier.

Was ist der leichteste Ansatz, um das Problem zu lösen?

Vielen Dank!

Alex Weinstein
quelle
4
Es gibt nicht viele Fälle, in denen eine Vererbung (oder sogar eine Mehrfachvererbung) gerechtfertigt ist. Schauen Sie sich die SOLID-Prinzipien an. Ziehen Sie die Komposition der Vererbung vor.
Ondřej Mirtes
2
@ OndřejMirtes was meinst du - "nicht viele Fälle, in denen eine Vererbung gerechtfertigt ist."?
styler1972
12
Ich meine - Vererbung bringt mehr Probleme als Vorteile (siehe Liskov-Substitutionsprinzip). Sie können fast alles mit Komposition lösen und viele Kopfschmerzen sparen. Die Vererbung ist auch statisch - das heißt, Sie können nicht ändern, was bereits im Code geschrieben ist. Compositition kann jedoch zur Laufzeit verwendet werden und Sie können Implementierungen dynamisch auswählen - z. B. dieselbe Klasse mit unterschiedlichen Caching-Mechanismen wiederverwenden.
Ondřej Mirtes
5
PHP 5.4 hat "Eigenschaften": stackoverflow.com/a/13966131/492130
f.ardelian
1
Ich würde Anfängern empfehlen, niemals Vererbung zu verwenden . Im Allgemeinen sind die einzigen zwei Situationen, in denen Vererbung zulässig ist ,: 1) beim
Erstellen

Antworten:

141

Alex, meistens ist eine Mehrfachvererbung ein Signal dafür, dass Ihre Objektstruktur etwas falsch ist. In der von Ihnen skizzierten Situation haben Sie die Klassenverantwortung einfach zu weit gefasst. Wenn die Nachricht Teil des Anwendungsgeschäftsmodells ist, sollte sie sich nicht um das Rendern der Ausgabe kümmern. Stattdessen können Sie die Verantwortung aufteilen und MessageDispatcher verwenden, der die über Text oder HTML-Backend übergebene Nachricht sendet. Ich kenne Ihren Code nicht, aber lassen Sie mich ihn folgendermaßen simulieren:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <[email protected]>';
$m->to = 'Random Hacker <[email protected]>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

$d = new MessageDispatcher();
$d->dispatch($m);

Auf diese Weise können Sie der Nachrichtenklasse eine Spezialisierung hinzufügen:

$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor

$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);

Beachten Sie, dass MessageDispatcher je nach typeEigenschaft im übergebenen Nachrichtenobjekt eine Entscheidung treffen würde, ob es als HTML oder als einfacher Text gesendet werden soll .

// in MessageDispatcher class
public function dispatch(Message $m) {
    if ($m->type == 'text/plain') {
        $this->sendAsText($m);
    } elseif ($m->type == 'text/html') {
        $this->sendAsHTML($m);
    } else {
        throw new Exception("MIME type {$m->type} not supported");
    }
}

Zusammenfassend ist die Verantwortung auf zwei Klassen aufgeteilt. Die Nachrichtenkonfiguration erfolgt in der InvitationHTMLMessage / InvitationTextMessage-Klasse, und der Sendealgorithmus wird an den Dispatcher delegiert. Dies nennt man Strategiemuster, mehr dazu lesen Sie hier .

Michał Rudnicki
quelle
13
Erstaunlich ausführliche Antwort, danke! Ich habe heute etwas gelernt!
Alex Weinstein
26
... Ich weiß, dass dies ein bisschen alt ist (ich habe gesucht, ob PHP MI hat ... nur aus Neugier). Ich denke nicht, dass dies ein gutes Beispiel für das Strategiemuster ist. Das Strategiemuster ist so konzipiert, dass Sie jederzeit eine neue "Strategie" implementieren können. Die von Ihnen bereitgestellte Implementierung bietet keine solche Fähigkeit. Stattdessen sollte Message die Funktion "send" haben, die MessageDispatcher-> dispatch () aufruft (Dispatcher entweder ein Parameter oder eine Mitgliedsvariable), und neue Klassen HTMLDispatcher & TextDispatcher implementieren "dispatch" auf ihre jeweilige Weise (dies ermöglicht anderen Dispatchern andere Arbeit)
Terence Honles
12
Leider eignet sich PHP nicht für die Implementierung von Strategiemustern. Sprachen, die das Überladen von Methoden unterstützen, funktionieren hier besser - stellen Sie sich vor, Sie haben zwei gleichnamige Methoden: dispatch (HTMLMessage $ m) und dispatch (TextMessage $) - jetzt verwenden Compiler / Interpreter in stark typisierten Sprachen automatisch die richtige "Strategie" basierend auf Art des Parameters. Abgesehen davon denke ich nicht, dass es das Wesentliche des Strategiemusters ist, offen für die Umsetzung neuer Strategien zu sein. Sicher ist es eine schöne Sache, aber oft keine Voraussetzung.
Michał Rudnicki
2
Angenommen, Sie haben eine Klasse Tracing(dies ist nur ein Beispiel), in der Sie allgemeine Dinge wie das Debuggen in eine Datei, das Senden von SMS für kritische Probleme usw. möchten. Alle Ihre Klassen sind Kinder dieser Klasse. Angenommen, Sie möchten eine Klasse erstellen Exception, die diese Funktionen haben soll (= Kind von Tracing). Diese Klasse muss ein Kind von sein Exception. Wie entwirft man solche Sachen ohne Mehrfachvererbung? Ja, Sie haben vielleicht immer eine Lösung, aber Sie werden immer dem Hacken nahe kommen. Und Hacking = auf lange Sicht teure Lösung. Ende der Geschichte.
Olivier Pons
1
Olivier Pons, ich denke nicht, dass die Unterklassenverfolgung die richtige Lösung für Ihren Anwendungsfall wäre. Etwas so Einfaches wie eine abstrakte Tracing-Klasse mit statischen Methoden Debug, SendSMS usw., die dann mit Tracing :: SendSMS () usw. aus jeder anderen Klasse heraus aufgerufen werden kann. Ihre anderen Klassen sind keine 'Arten' von Tracing. Sie "verwenden" Tracing. Hinweis: Einige Benutzer bevorzugen möglicherweise einen Singleton gegenüber statischen Methoden. Ich bevorzuge statische Methoden gegenüber Singletons, wo dies möglich ist.
15

Vielleicht können Sie eine 'is-a'-Beziehung durch eine' has-a'-Beziehung ersetzen? Eine Einladung enthält möglicherweise eine Nachricht, muss jedoch nicht unbedingt eine Nachricht sein. Möglicherweise wird eine Einladung bestätigt, die nicht gut zum Nachrichtenmodell passt.

Suchen Sie nach "Komposition vs. Vererbung", wenn Sie mehr darüber wissen möchten.

Matthias Kestenholz
quelle
9

Wenn ich Phil in diesem Thread zitieren kann ...

PHP unterstützt wie Java keine Mehrfachvererbung.

In PHP 5.4 werden Merkmale enthalten sein, die versuchen, eine Lösung für dieses Problem bereitzustellen.

In der Zwischenzeit sollten Sie Ihr Klassendesign am besten überdenken. Sie können mehrere Schnittstellen implementieren, wenn Sie nach einer erweiterten API für Ihre Klassen suchen.

Und Chris ...

PHP unterstützt die Mehrfachvererbung nicht wirklich, aber es gibt einige (etwas chaotische) Möglichkeiten, sie zu implementieren. Schauen Sie sich diese URL für einige Beispiele an:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

Ich dachte, beide hätten nützliche Links. Ich kann es kaum erwarten, Eigenschaften oder vielleicht ein paar Mixins auszuprobieren ...

Simon East
quelle
1
Eigenschaften sind der richtige Weg
Jonathan
6

Das Symfony-Framework verfügt über ein Mixin-Plugin , das Sie vielleicht ausprobieren möchten - auch nur für Ideen, wenn Sie es nicht verwenden möchten.

Die Antwort "Entwurfsmuster" besteht darin, die gemeinsam genutzte Funktionalität in eine separate Komponente zu abstrahieren und zur Laufzeit zu komponieren. Überlegen Sie, wie Sie die Einladungsfunktionalität als eine Klasse abstrahieren können, die Ihren Nachrichtenklassen auf andere Weise als durch Vererbung zugeordnet wird.

joelhardi
quelle
4

Ich verwende Merkmale in PHP 5.4, um dies zu lösen. http://php.net/manual/en/language.oop5.traits.php

Dies ermöglicht eine klassische Vererbung mit Erweiterungen, bietet aber auch die Möglichkeit, gemeinsame Funktionen und Eigenschaften in ein Merkmal zu integrieren. Wie das Handbuch sagt:

Traits ist ein Mechanismus zur Wiederverwendung von Code in einzelnen Vererbungssprachen wie PHP. Ein Merkmal soll einige Einschränkungen der Einzelvererbung verringern, indem es einem Entwickler ermöglicht wird, Methodensätze in mehreren unabhängigen Klassen, die in unterschiedlichen Klassenhierarchien leben, frei wiederzuverwenden.

Matthew Pearson
quelle
3

Es hört sich so an, als wäre das Dekorationsmuster geeignet, aber ohne weitere Details schwer zu sagen.

danio
quelle
Beste Antwort IMO.
100k
3

Dies ist sowohl eine Frage als auch eine Lösung ....

Was ist mit dem magischen _ call (),_get (), __set () Methoden? Ich habe diese Lösung noch nicht getestet, aber was ist, wenn Sie eine MultiInherit-Klasse erstellen? Eine geschützte Variable in einer untergeordneten Klasse kann ein Array von zu erbenden Klassen enthalten. Der Konstruktor in der Multi-Interface-Klasse könnte Instanzen jeder der geerbten Klassen erstellen und sie mit einer privaten Eigenschaft verknüpfen, z. B. _ext. Die Methode __call () kann die Funktion method_exists () für jede der Klassen im Array _ext verwenden, um die richtige aufzurufende Methode zu finden. __get () und __set können verwendet werden, um interne Eigenschaften zu suchen, oder wenn Sie ein Experte mit Referenzen sind, können Sie die Eigenschaften der untergeordneten Klasse und der geerbten Klassen als Verweise auf dieselben Daten festlegen. Die Mehrfachvererbung Ihres Objekts wäre für den Code mit diesen Objekten transparent. Ebenfalls, Interne Objekte können bei Bedarf direkt auf die geerbten Objekte zugreifen, solange das Array _ext nach Klassennamen indiziert ist. Ich habe mir vorgestellt, diese Superklasse zu schaffen, und habe sie noch nicht implementiert, da ich der Meinung bin, dass wenn sie funktioniert, dies dazu führen könnte, dass sich unterschiedliche Programmiergewohnheiten entwickeln.

Ralph Ritoch
quelle
Ich denke das ist machbar. Es wird die Funktionalität mehrerer Klassen kombinieren, sie jedoch nicht (im Sinne von instanceof) erben
user102008
Und dies wird sicherlich keine Überschreibungen zulassen, sobald in der inneren Klasse ein Aufruf an sich selbst erfolgt :: <was auch immer>
Phil Lello
1

Ich muss ein paar Fragen stellen, um zu klären, was Sie tun:

1) Enthält Ihr Nachrichtenobjekt nur eine Nachricht, z. B. Text, Empfänger, Zeitplan? 2) Was haben Sie mit Ihrem Einladungsobjekt vor? Muss es im Vergleich zu einer EmailMessage speziell behandelt werden? 3) Wenn ja, WAS ist das Besondere daran? 4) Wenn dies der Fall ist, warum müssen die Nachrichtentypen für eine Einladung anders behandelt werden? 5) Was ist, wenn Sie eine Willkommensnachricht oder eine OK-Nachricht senden möchten? Sind sie auch neue Objekte?

Es hört sich so an, als würden Sie versuchen, zu viele Funktionen in einer Reihe von Objekten zu kombinieren, bei denen es nur darum gehen sollte, den Inhalt einer Nachricht zu speichern - und nicht darum, wie damit umgegangen werden soll. Sie sehen, es gibt keinen Unterschied zwischen einer Einladung oder einer Standardnachricht. Wenn die Einladung eine spezielle Behandlung erfordert, bedeutet dies Anwendungslogik und keinen Nachrichtentyp.

Beispiel: Ein von mir erstelltes System hatte ein gemeinsames Basisnachrichtenobjekt, das auf SMS, E-Mail und andere Nachrichtentypen erweitert wurde. Allerdings: Diese wurden nicht weiter erweitert - eine Einladungsnachricht war einfach ein vordefinierter Text, der über eine Nachricht vom Typ E-Mail gesendet werden sollte. Ein bestimmter Einladungsantrag würde sich mit der Validierung und anderen Anforderungen für eine Einladung befassen. Schließlich möchten Sie nur die Nachricht X an den Empfänger Y senden, der ein eigenständiges System sein sollte.


quelle
0

Gleiches Problem wie Java. Versuchen Sie, Schnittstellen mit abstrakten Funktionen zu verwenden, um dieses Problem zu lösen

DeeCee
quelle
0

PHP unterstützt Schnittstellen. Dies könnte abhängig von Ihren Anwendungsfällen eine gute Wahl sein.

Cheekysoft
quelle
5
Schnittstellen erlauben keine konkreten Funktionsimplementierungen, daher sind sie hier nicht hilfreich.
Alex Weinstein
1
Schnittstellen unterstützen im Gegensatz zu Klassen die Mehrfachvererbung.
Craig Lewis
-1

Wie wäre es mit einer Einladungsklasse direkt unter der Nachrichtenklasse?

So lautet die Hierarchie:

Nachricht
--- Einladung
------ TextMessage
------ EmailMessage

Fügen Sie in der Einladungsklasse die Funktionen hinzu, die in InvitationTextMessage und InvitationEmailMessage enthalten waren.

Ich weiß, dass Einladung nicht wirklich eine Art von Nachricht ist, sondern eher eine Funktionalität von Nachricht. Ich bin mir also nicht sicher, ob dies ein gutes OO-Design ist oder nicht.

Nube
quelle