Moin!
Grundsätzlich habe ich das meiste soweit verstanden. Doch einiges wirft mehr fragen auf, als es beantwortet :)
Ausgezeichnet!
Die Parameter im Konstruktor der Navi-Klasse
class Navi
{
#...
public function __construct(array $links, Navi_Decorator $decorator)
{
# ...
}
}
>
> Das meiste in deinem Bsp. ist selbstredend, nur der 2. Parameter. Warum steht da die Klasse Navi\_Decorator drin, diese wird doch später als neues Objekt Seperat aufgerufen? Und wie nennt sich die Schreibweise für diese Parameter, also das neben der Variable "array" steht, oder der Klassenname (hier "Navi\_Decorator")?
Das ist ein sogenannter Type-Hint (bei beiden Parametern). Er bewirkt, dass PHP sich beschwert, wenn der übergebene Wert einen anderen Typ hat, als im Hint drinsteht.
Für den Decorator-Parameter bedeutet dass: Die Prüfung `$decorator instanceof Navi_Decorator`{:.language-php} muss true ergeben. Das ist der Fall, wenn $decorator entweder ein Objekt der Klasse Navi\_Decorator ist, oder von einer Klasse erbt, die Navi\_Decorator ist, oder ein Interface implementiert, welches Navi\_Decorator heißt, oder von einer Klasse erbt, welche ein Interface Navi\_Decorator implementiert.
So ein Type-Hint erzwingt, dass der übergebene Parameter ein Objekt ist, in dem gewisse Methoden existieren - denn ein Interface definiert, welche Methoden ein Objekt zu implementieren hat, und eine Vererbung von einer Klasse garantiert ebenfalls, dass die Methoden der Klasse vorhanden sind und aufgerufen werden können.
Da die Klasse Navi\_Decorator in meinem Beispiel genau eine Methode namens "getHtmlLink()" implementiert, kann ich durch den Typehint garantieren, dass das übergebene Objekt eines ist, welches diese Methode hat. Ich muss beim späteren Verwenden dieses dann intern abgespeicherten Objektes nicht mehr prüfen, ob es diese Methode gibt, und es kann auch nicht passieren, dass irgendein unwissender anderer Programmierer mir ein Objekt rein reicht, bei dem dieser Aufruf scheitert, weil seine Klasse ganz andere Methoden kennt.
> Nächste Frage wäre:
>
> ~~~php
class Navi_Link
> {
> public $label = "";
> public $url = "";
> public $target = "";
> }
Die jetzt anstehende Änderung wäre also, anstelle eines simplen Strings in einem Array lieber ein Array von diesen Klassen in die Navigation hinein zu tun.
Wie darf ich ein "Array von diesen Klassen" verstehen? Soll ich jeden einzelnen Link des Menus in einem Objejt speichern?
Wäre das nicht zuviel Overhead, wenn viele Links exisitieren?
Angenommen, diese drei oben genannten Informationen gehörten zu deinem Link dazu. Dann hast du die Wahl, entweder ein Objekt zu nehmen, oder ein Array, um diese drei Werte zu gruppieren. Ein Objekt ist dabei im Prinzip genauso groß, wie ein Array, denn der Code der zugehörigen Klasse wird sowieso nur einmal im Speicher gehalten, entscheidend sind lediglich die Objektvariablen. Und die sind in beiden Fällen identisch, nämlich drei Strings.
Ich will nicht abstreiten, dass es eventuell doch Unterschiede in der Speichergröße geben könnte. Aber diese sind zu vernachlässigen. Du wirst mit zehn Links kein Problem haben - und wenn du ein Problem kriegst, dann ist es relativ egal, ob du bei 10.000 Links oder bei 23.615 Links ein Problem kriegst - die zu ergreifende Lösung wäre in jedem Fall, sich die Link-Information dann nicht auf einen Schlag komplett in den Speicher zu legen, sondern im Bedarfsfall zu holen. Dann wiederum bist du allerdings mit Objekten als Datenspeicher deutlich besser dran, denn nur damit könntest du sowas einfach realisieren. Ein komplettes Array hingegen kann nur plump im Speicher liegen - entweder es passt rein, oder nicht.
Das "Array von Klassen" ist sowas:
$linkHome = new Navi_Link();
$linkHome->label = "Home";
$links = array($linkHome);
Es ist an dieser Stelle aber nicht sehr schön, dass Objekte in einem Array lagern - es ist kompliziert, die dort manuell reinzukriegen, und erzeugt irgendwann vielleicht die gerade erwähnten Platzprobleme.
- Eine Liste von Links in ein Array zu packen, wenn man doch den Datenbankzugriff ebenfalls objektorientiert erledigen will, ist vielleicht keine so wahnsinnig gute Idee.
Ich habe es bei mir bislang so gelöst, dass ich das Resultat aus der Datenbank direkt als Linkliste speichere, ohne erst in ein Array zu kopieren. Das ist ja noch mit das einfachste.
Ja, das ist die prozedurale Herangehensweise: Datenbankabfrage und Erzeugung des HTML-Ausgabestrings ist wild durchmischt - am besten steht alles noch insgesamt in irgendeinem Template drin.
In OOP trennt man die Dinge inhaltlich voneinander. Datenbankabfrage hat mit HTML-Erzeugung nichts zu tun, lediglich die aus der DB abgefragten Daten werden irgendwie zur Ausgabe transferiert.
Am Ende geschieht, wenn man die Objektorientierung gut gemacht hat, im Code entrollt ziemlich genau dasselbe: Ein Query wird losgeschickt, ein DB-Ergebnis entsteht, es wird einzeln abgefragt und in HTML-Links verwandelt. Der Unterschied ist lediglich, dass während dieser Ausführung deutlich mehr Funktionsaufrufe passieren, die innerhalb der beteiligten Klassen hin- und herwechseln, um jeweils eine notwendige Teilaufgabe zu erledigen. Der Programmierer hingegen sieht im Code diese Wechsel nicht so deutlich, sondern konzentriert sich innerhalb einer Klasse auf einen möglichst simplen Teilaspekt.
Auf diese Weise kann man durch OOP komplexe Probleme mit relativ einfachem Code lösen, und sehr komplexe Probleme überhaupt erst bewältigen. Die Erzeugung eines Menüs ist kein komplexes Problem, sondern eigentlich noch relativ einfach, weshalb diese komplexitätsreduzierende Wirkung von OOP eigentlich eher lächerlich wirkt - deshalb nicht abschrecken lassen. :)
Außerdem bietet PHP die Möglichkeit, dass Objekte ebenfalls mit "foreach" durchlaufen werden können, indem man das Iterator- oder das IteratorAggregate-Interface implementiert. Man würde also kein Array mehr in "Navi" hineingeben, sondern ein iterierbares Objekt "Navi_Linklist", welches dann einzelne "Navi_Link"-Objekte ausspuckt.
Hier stehe ich komplett auf den Schlauch.
Es gibt das Interface "Iterator". Siehe http://de3.php.net/manual/de/language.oop5.iterations.php, Beispiel 2.
Dieses Interface verlangt vom Programmierer das Schreiben von fünf Methoden in seiner Klasse, die in einer definierten Reihenfolge aufgerufen werden, wenn man ein Objekt dieser Klasse mit "foreach" durchlaufen will.
Ausgangsvoraussetzung: Du willst ein Objekt haben, dass in irgendeiner Weise eine "Liste" von Werten ist (was auch immer diese Werte sind). Für das Menü-Problem brauchst du eine Liste von Links, und die Links selbst sind Objekte vom Typ "Navi_Link".
Du möchtest folgenden Code haben:
$linkliste = $db->getQueryErgebnisMitLinkliste(); // Was auch immer das tut... irgendwas mit Datenbank
foreach ($linkliste as $link)
{
// hier was mit dem einzelnen Link machen
}
Wenn in $linkliste ein Objekt vom Typ "Navi_Linkliste" enthalten ist, muss dieses Objekt:
1. intern eine Liste der Links verwalten
2. nach außen die fünf Methoden des Interfaces implementieren, um
2a. mit der Methode "rewind()" die Liste auf den Anfang zu spulen,
2b. mit der Methode "valid()" anzuzeigen, dass der "Listenzeiger" auf einem gültigen Element steht,
2c. mit der Methode "current()" genau dieses Element zurückzugeben,
2d. mit der Methode "key()" den Schlüssel dieses Elements zurückzugeben (das ist in einem Array der Index)
2e. mit der Methode "next()" den Listenzeiger auf das nächste Element setzen.
Diese Listenoperationen sind ganz nah an den für Arrays verfügbaren Funktionen "reset()", "current()" und "next()" sowie "each()".
Man muss aber seine Liste intern nicht in einem Array abspeichern. Beispielsweise kann man stattdessen intern ein Result-Objekt von mysqli abspeichern. Dieses Objekt repräsentiert das Ergebnis eines Querys, und es bietet nette Funktionen an, mit denen man das Ergebnis schrittweise auslesen kann:
rewind() ruft data_seek(0) auf, um zurückzuspulen, und liest den nächsten Datensatz mit fetch_object() aus. Der Erfolg wird als Flag gespeichert, genauso wie dieser Datensatz.
valid() liest das Erfolgsflag aus.
current() gibt das gerade ausgelesene Ergebnis zurück.
key() könnte den primary Key aus dem Ergebnis kennen und ausgeben, oder es wird intern die Anzahl der Datensätze mitgezählt und als Key ausgegeben.
next() liest den nächsten Datensatz mit fetch_object() aus und aktualisiert das Erfolgsflag.
- Sven Rautenberg