Diskussion / Test: Beliebig erweiterbare Klassen

ZodiacXP

Erfahrenes Mitglied
Eigentlich könnte der Titel auch "Multiple-erweiterbare Klassen" oder "Mehrfachdefinition von Methoden" sein, so wie es in diesen beiden Threads oft ein Problem war:

Lösungen konnten durch Aspects, das runkit oder "Stubbles - Extending Objects With New Methods At Runtime" erreicht werden.

Leider sind dies keine Hausmittel bzw. können diese Konzepte nur umständlich Methoden ersetzen / erweitern. Daher habe ich mich mit einem Freund zusammengesetzt und eine Klasse "Extensionable" geschrieben, die im Anhang zu finden ist.

Wie ist die Klasse zu verstehen?
Die bereits in eurem System enthaltenen und laufenden Klassen können nun die Schnittmenge mehrerer anderer Klassen repräsentieren und behalten ihren eigenen Namen bei.
Das ganze hört sich nach viel Speicher an, jedoch wurde es so ausgelegt, dass wirklich nur die nötigen und neuen Methoden eingebunden werden. Es ist ein Gemisch aus Strategy-Pattern, Bäumen und SkipLists.

Kosten?
Leider ist die Laufzeit der eizelnen Aktionen noch nicht berechnet aber die schätze ich auf O(log n) [mit n als Anzahl der Erweiterungen], da durch eine einfache SkipList iteriert wird.
Jedes Blatt (wenn man alle Erweiterungen als Baum sieht) kostet ein Objekt im Speicher, was durch das Strategy-Pattern bedingt ist.

Beispiel:
Ihr habt eine Klasse für den View. Diese soll multilingual werden und gleichzeitig um Zugriffsrechte erweitert werden. Später kommt zu dem multilingualen noch sprechende URLs hinzu. Leicht lösbar:
PHP:
class View extends Extensionable
{ ... }

// mehrere Sprachen
Hook::registerExtension("MultiLang", "View");
class MultiLang extends View {...}

// sprechende URLs
Hook::registerExtension("URL", "MultiLang");
class URL extends MultiLang {...}

// Zugriffsrechte
Hook::registerExtension("Rights", "View");
class Rights extends View {...}

$x = new View(); // verhält sich wie Rights, URL und View selbst

Bitte beachten: Bisher nur argumentlose Konstruktor und Methoden!
 

Anhänge

Interessant. Aber worin besteht der Vorteil gegenüber schlichter Vererbung? Dieses System stelle ich mich unglaublich schwer zu warten und vor allem schwer zu debuggen.

Versteh mich bitte nicht falsch, aber ich habe gern ein nach oben hin abgeschlossenes System. Ich sehe auch noch keinen Use-Case für solch ein Pattern.

Was mir auch ein bisschen missfällt an dieser Stelle ist, das eine public static Member-Variable benötigt wird. Könnte man Extensionable nicht von Hook erben lassen? Bzw. was bringt es für Vorteile, die Extensions in einer separaten Klasse abzulegen?

Man könnte wenigstens einen Getter einbauen (für die Extensions) und die Member-Var private deklarieren.

Nenn doch bitte mal ein paar Use-Cases und Vorteile gegenüber klassischer Vererbung.
 
Also ich finde den Ansatz auch interessant, da ich persönlich auch schon vor dem Problem stand, dass ich eine Klasse hatte, die aber von mehreren Klassen erben sollte. Das ist leider in dieser Form bei PHP nicht möglich, deshalb bin ich dir schon jetzt dankbar dafür, dass mal jemand über eine Lösung dieses Problems nachdenkt.
 
Kurze Frage: Wieviele Mütter/Väter hast du?

Gegeben sei Klasse A, B und C; A und B sind Klassen, die Funktionalität bereit stellen, die in C gebraucht werden könnten.

Wäre es nicht logischer, C von A erben zu lassen und anschließend eine Instanz von B innerhalb von A als Member-Variable zu erzeugen?
 
Das mag sein, dass man vom rein logischen immer nur einen Vater und eine Mutter hat, aber Interfaces können auch von mehreren Interfaces erben. Dementsprechend wäre solch eine Möglichkeit für Klassen auch schön. Aber du hast schon Recht, dass man ansonsten eine Hauptklasse hat, welche in sich dann weitere Instanzen von anderen Klassen führt, so wie ich es bisher gelöst habe. Wenn man denen dann noch eine Referenz auf $this mit gibt, dann können die anderen Methoden auch problemlos die Hauptklasse verändern.
 
Ich bin deiner Meinung und sage ja auch nicht, das ich das für überflüssig halte. Mir fällt kein Fall ein, bei dem ich diesem Entwurfsmuster (könnte ja ein neues sein/werden) den Vorzug gegenüber der Vererbung geben würde.

Aber das kann ZodiacXP ja noch erläutern.

Das Thema Interface halte ich deshalb für indiskutabel, weil ein Interface nun mal eine Schnittstelle ist. Das bedeutet, andere Klassen finden über die Implementierung des beliebigen Interface eine Schnittstelle, an die sie andocken können. Daher macht das auch Sinn, das eine Klasse mehrere Schnittstellen einbinden kann. Man kann ja beliebig viele Freunde haben, die irgendwelche Sachen von einem wollen, wenn du verstehst, was ich meine :-)

Aber zum Test: Der ging, wie zu erwarten war, korrekt aus.
 
Ja, es stimmt zwar auch, dass eine Klasse mehrere Interfaces haben kann, aber was ich meinte war, dass auch Interfaces von Interfaces erben können, nur eben im Gegensatz zu Klassen von mehreren Interfaces gleichzeitig, solange diese keine gleichen Methoden definieren.
 
Danke das ihr so rege an der Diskussion teilnehmt. Schön eine Partei zu sehen die eher dafür ist und eine die eher dagegen ist :) Da kommen die Vor- und Nachteile richtig zur Geltung.

Unser Ansatz richtet sich mehr an das Problem, welches "einfach nur crack" beschreibt.
Eine Klasse soll von mehreren anderen Erben. Aber zunächst mal eine Antwort auf die Fragen:

Interessant. Aber worin besteht der Vorteil gegenüber schlichter Vererbung? Dieses System stelle ich mich unglaublich schwer zu warten und vor allem schwer zu debuggen.

Schlichte Vererbung ist wie du selbst mit Mutter/Vater zeigst "Monogam" ;) Mit dem Hook als "Registrierungsstelle" und der Klasse können mehrere gleichzeitig genutzt werden. Klar, das löst jeder bisher durch Decorator- oder Facade-Pattern. Jedoch blähen diese Ansätze den Code und auch den Speicher mehr auf als die hier vorgestellte lösung, da die "Registrierungsstelle" weis, wie vererbt wurde und so nur die Endknoten und somit wirklich relevanten Klassen berücksichtigt. Insofern ist das Warten und Debuggen von gleichem Aufwand (wenn nicht sogar geringer) als bei dem Decorator/Facade.


Versteh mich bitte nicht falsch, aber ich habe gern ein nach oben hin abgeschlossenes System. Ich sehe auch noch keinen Use-Case für solch ein Pattern.

Klar, ich hab abgeschlossene Systeme auch gern. Dieses Problem besteht dennoch zum Beispiel bei Systemen welche verschiedene Erweiterungen dynamisch aufnehmen (Beispiel: CMS, Frameworks, etc.). Da ist es super nicht in den Quelltext vom System eingreifen zu müssen, sondern einfach seine Erweiterung "anzuhängen".

Was mir auch ein bisschen missfällt an dieser Stelle ist, das eine public static Member-Variable benötigt wird. Könnte man Extensionable nicht von Hook erben lassen? Bzw. was bringt es für Vorteile, die Extensions in einer separaten Klasse abzulegen?

Das wurde separiert um Speicher zu sparen. Sonst würden die Variablen und Infos überall kreuz und quer in allen Klassen rumliegen.

Man könnte wenigstens einen Getter einbauen (für die Extensions) und die Member-Var private deklarieren.

Wie sähe hier dein Ansatz aus?

--

So hoffe die Vorteile gg. klassischer Vererbung sind hinreichend erklärt worden. Sonst gerne fragen.

Es geht halt darum ein laufendes System so gut wie nicht anzufassen und es trotzdem Erweitern zu können. Mit dieser Lösung ist es besonders schön weil selbst die Erweiterungen mehrfach erweitert werden können.

Die Use-Case hierzu wäre das rasche lösen von neu hinzugekommenen Use-Cases, die bei einem abgeschlossenen System bisher nicht beachtet worden sind ;) Beispiel: CMS, Foren, Wiki und sonstige Informationssysteme denen neue Anforderungen zukommen sollen wie Multilinguale Seiten, sprechende URL, Rechtesysteme, und was der Wandel noch so mit sich bringt. So kann sehr kurzfristig auf Änderungen eingegangen werden, während die "Neu-Modellierung" des Systems noch stunden-/tagelang geplant wird.
 
Wie sähe hier dein Ansatz aus?

Ich würde $extension im Hook privat deklarieren und einen public static Sucher dafür schreiben:

PHP:
class Hook
{
	private static $extension;
	
	public static function registerExtension($add, $orig)
	{
		self::$extension[$orig][$add] = $add;
	}

        public static function hasExtension($extension)
        {
                return in_array($extension, self::$extension);
        }
}

Innerhalb der Extensionable dann ein Zugriff über den Sucher, der true zurückgibt, wenn es die gewünschte Klasse gibt.

Macht logisch keinen Unterschied, sieht aber schöner aus und keiner pfuscht zur Laufzeit in der Hook-Property rum.

--------

Soweit habe ich den Ansatz und seine Vorteile verstanden. Kannst du schon sagen, was da an Ressourcen eingespart wird?
 
Klar! Wo war ich denn grad mit meinen Gedanken!?
Ach, mit dem "setter" (registerFunction) soll nur verhindert werden, dass eine Erweiterung falsch angehängt wird. Eigentlich kann der setter eher weg, was auch höchstwahrscheinlich passieren wird um Lightweight entgegen zu kommen.
 
Zuletzt bearbeitet:
Zurück