Moin!
Komponenten von bereits existierenden Frameworks oder anderen Drittanbietern sollen laut Aufgabenstellung nicht verwendet werden.
Wäre ja auch noch schöner, wenn man sich viel Arbeit spart und einfach fremden Code importiert... ;->
Beispiele:
Um das "Problem" zu verdeutlichen, möchte ich von einer Klasse ausgehen, die Konfigurationsdaten aus verschiedenen Quellen (z.B. INI, XML, ...) lädt und anschließend für späteren Zugriff speichert. Folgende Möglichkeiten zur Umsetzung haben wir uns bisher überlegt:
Nur weil "fremde Frameworks" nicht benutzt werden dürfen, muss man sie ja nicht auch als Inspirationsquelle ignorieren.
Nun hast du mit "Konfiguration" ausgerechnet das Themengebiet ausgesucht, das viele diskussionswürdige Aspekte enthält. "Die Konfiguration der Software" ist nämlich sowohl dynamisch (je nach eingelesener Datenquelle), als auch statisch (es kann nur eine Variante zur Zeit geben), sowohl global (die Konfiguration gilt überall gleich) als auch lokal (nicht jede Konfigurationsvariable ist überall gültig und/oder relevant) - und letztendlich ist immer die Frage: Wie kommt die nutzende Klasse konkret an die gewünschten Werte?
- Einzelne Klasse
Die Klasse enthält lediglich je eine eigene Funktion für die möglichen Quellen.
WENN ich die Konfiguration in einer einzelnen Klasse ablegen würde, dann darin direkt "eingemauert" als PHP-Code, und nicht von irgendwoher eingelesen. Die Methoden dieser Klasse erlauben dann den Zugriff auf die Werte, und eventuell kann man auch irgendwie umstellen, welche Variante tatsächlich ausgeliefert wird.
- Vererbung
Unterklasse ist beispielsweise "Ini", Oberklasse "Config". "Ini" parsed die Ini-Datei und speichert die Konfigurationen wegen der Vererbung "quasi in sich selbst". "Config" ist als abstrakt definiert, sodass nur "Ini" direkt aufgerufen werden kann. Es werden also die einzelnen Funktionen aus 1) in eine eigene Klasse ausgegliedert.
Vererbung von Klassen ist dann schön, wenn:
- es mehr als eine erbende Klasse gibt (wenn das mal irgendwann "sein könnte", gilt: YAGNI)
- diese zwei oder mehr Klassen eine gemeinsame Codebasis haben, die man durch das Verlagern in eine Elternklasse vereinheitlichen kann
- sich dadurch in einem Bereich nicht mehr als drei Vererbungsebenen ergeben (zuviel Vererbung macht den Code undurchsichtig und damit schwer wartbar): Foo_Bar_Baz extends Foo_Bar_Abstract, Foo_Bar_Abstract extends Foo_Abstract - mehr nicht. Entscheidend für die "Vererbungsebenen" ist allerdings, welche Trennstellen man definieren kann. Wenn Foo_Bar_Baz eine Klasse eines externen Frameworks ist, die man seinerseits nochmal erweitern will, dann wäre diese Vererbung wohl die zweite Ebene, weil "Bereich" sich nur auf den eigenen Einflussbereich bezieht, auch wenn man dadurch insgesamt vier Vererbungsstufen hat.
Es ist übrigens sehr empfehlenswert, sich von Anfang an ein vernünftiges Namensschema zur Klassenbenennung zu definieren - insbesondere, weil man heutzutage definitiv Autoloading benutzen will. Für sowas gibts schon Standards, der einzig relevante für PHP dürfte "PSR-0" sein (PSR steht für PHP Standard Recommendation, die 0 ist die laufende Nummer). Das Zend-Framework setzt dieses Namensschema ein, und ich halte es für sehr empfehlenswert.
Insofern kannst du eigentlich keine Klasse "Ini" haben, wenn die von "Config" erbt, sondern müsstes sie "Config_Ini nennen. Rein "zufällig" hat das Zend-Framework sowas schon vorgemacht: "Zend_Config" ist die Hauptklasse, aus der Konfigurationswerte herauskommen, und Zend_Config_Ini ist die Klasse, die Ini-Dateien einliest und parst. Die Frage wäre, ob Config_Ini von Config erben muss. Im Zend-Framework wird das gemacht, ich würde es aber nicht als zwingend ansehen.
- "Helper"-Klasse
Klasse "Ini" implementiert eine Interface "Sources". Im Konstruktor von Klasse "Config" wird der Quellen-Typ ermittelt und dementsprechend eine der Quellen-Klassen, also z.B. "Ini", aufgerufen, welche anschließend ein Array von Konfigurationsdaten zurückliefert. Dieses Array wird anschließend von "Config" gespeichert. ("Ini" erbt nicht von "Config")
Klingt wie Zend-Framework. :)
Interfaces sind ja dafür da, das Vorhandensein von öffentlichen Methoden zu garantieren. Das lohnt sich also nur, wenn das Interface wirklich in mehr als einer Klasse verwendet wird. Ich habe schon Designs gesehen, in denen ein leeres Interface (keine definierten Methoden) einfach nur "weil man es so macht" definiert und von einer einzigen Klasse implementiert wurde.
- Factory Method
Eine Mischung aus 2) und 3). Unterschied: Oberklasse "Config" besitzt eine statische Fabrik-Methode. Statt wie in 2) muss diese bemüht werden, den richtigen "Ansprechpartner" zu finden und nicht der Konstruktor. Der Konstruktor ist in diesem Fall mindestens "protected".
Klingt nicht wie das Factory-Pattern, sondern wie das Singleton-Pattern. Und DAS sollte man meiden! Es ist nämlich nicht gut testbar.
- Dependency Injection (Verwendet der nun folgende Fall überhaupt dieses Pattern?)
Eine Mischung aus 3) und 4). Unterschied: Hier bestimmt der Konstruktor von "Config", welcher Quellen-Typ vorliegt und instanziiert die entsprechende Helfer-Klasse. Die Helferklasse erhält eine Referenz von "Config", in die sie mittels der Methoden von "Config" Eigenschaften speichern kann.
Dependency Injection ist, wenn ein Objekt zum Funktionieren ein anderes Objekt benötigt, welches es aber nicht selbst erzeugt, sondern von Außen reingereicht bekommt.
Wenn der Konstruktor des Objekts also selbst was instanziiert, dann ist das KEINE Dependency Injection.
Auch hier ist wieder das Stichwort "Testbarkeit" ausschlaggebend. Wenn man ein Objekt testen will, dass von ganz allein andere Objekte instanziiert und benutzt, dann kann man das eigentliche Objekt nicht vollständig testen. Denn man muss auch kontrollieren können, ob und wie das zu testende Objekt sein inneres Objekt anspricht. Sowas geht nur, wenn man anstatt des echten inneren Objekts eine Testklasse hineintut, die die Methodenaufrufe registriert und mit vorgefertigten Resultaten antworten kann.
Frage:
Also nun meine Frage an Euch: Welche Möglichkeiten haltet Ihr noch für denkbar? Was haltet Ihr von den oben genannten Möglichkeiten? Welche Vor- oder Nachteile fallen Euch ein? Was würdet Ihr ganz persönlich bevorzugen, wenn Ihr mit einem Framework arbeitet?
Was ich bevorzuge: Dokumentation, Testabdeckung und Nutzung etablierter Standards, und nicht die hundertdreiundzwanzigste Neuerfindung eines MVC-Frameworks.
Ich meine: Ich kann's ja verstehen, wenn man ein Framework nicht mag, weil es die eigene Philosophie nicht unterstützt. Deswegen gibt's ja schon mehr als eins. Und gerade das Zend Framework deckt schon extrem viele typische Aufgaben ab, denen man im Web-Kontext immer wieder begegnet.
Wir freuen uns über alle Meinungen, Anmerkungen, Vorschläge oder was-auch-immer, was uns bei unserer Entscheidungsfindung behilflich sein könnte.
Solltet Ihr Fragen zu oben beschriebenem haben, habe ich mich unklar ausgedrückt oder etwas vergessen, so schreibt bitte einfach kurz!
Existierende Frameworks angucken. Benutzen. Ideen erkennen. Und wenn's sein muss, dann nachbauen.
- Sven Rautenberg