Moin!
Nur mal eine Grundsatzfrage zu OOP, die ich bislang nicht ganz verstanden habe.
Ich möchte mein Menu-Script in eine Klasse einschliessen.
Das wäre soweit kein Problem. Allerdings benutze ich das Script in erweiterter Form. Ich erstelle damit unterschiedliche Menus, also Main-Navi, Footer-Navi u.s.w.
Gehe zunächst nicht davon aus, was du im Inneren der Klasse alles tun musst. Gehe vom Äußeren aus: Wie willst du deine Klasse verwenden? Was ist "einfach" in der Anwendung?
OOP zu benutzen zerfällt im Prinzip in zwei Phasen:
Erstens: Initialisieren und konfigurieren für die zweite Phase.
Zweitens: Benutzen für den gewünschten Zweck.
Bei der Betrachtung des Nutzungszwecks und der Einfachheit in der Anwendung sollte man sich nicht von den möglicherweise komplizierten Konstruktionen der ersten Phase beeinflussen lassen - dafür findet man ebenfalls eine Lösung.
Deshalb die Frage: Was willst du eigentlich "genau" tun? Welche tatsächlichen Anwendungsfälle willst du haben? Es reicht, mit dem ersten anzufangen. :)
Muss ich jetzt, wenn ich das Menu in eine Klasse packe und die unterschiedlichen Menus anfordere, jeweils ein Neues Objekt erzeugen, da alles Unterschiedliche "Menus" sind. Oder lade ich das Menu in ein Objekt und rufe die verschiedenen Menus mittels Methoden und entsprechenden Parametern auf? Was ist hier der bessere Stil?
Hängt davon ab, wie du es benutzen willst.
Was ist dein Anwendungsfall? Offenbar: "Gib mir ein Menü aus!" Halt, nicht ganz: "Gib mir zwei unterschiedliche Menüs aus!"
Was ist der einfachste Code, den du dir vorstellen kannst, um die Ausgabe _eines_ Menüs zu starten?
Sowas:
$navi = new Navi();
$navi->writeMenu();
Was wäre die einfachste Implementierung, die in dieser Funktion drinsteckt? Vermutlich sowas:
echo "Menü....";
Ja, absolut banal, aber als Resultat hast du eine Klasse, einen Methodenaufruf und ein ausgegebenes Menü! Das erfüllt den ersten Case "Gib mir ein Menü aus!" absolut.
Jetzt kommt allerdings der Test-Nazi in mir durch und sagt: Ein "echo" in der Funktion kann man schlecht testen, die Menüfunktion sollte deshalb lieber einen String des Menüs zurückgeben, anstatt ihn direkt auszugeben. Das "echo" zum Browser sollte man lieber "außerhalb" regeln. Also bauen wir die Klasse mal kurz um: "echo" fliegt raus, wird durch "return" ersetzt, und die Methode heißt "getHtmlMenu()".
Nächstes Problem: "Zwei unterschiedliche Menüs"...
"Gleich" ist kein Problem, einfach die Klasse zweimal instantiieren und an zwei Stellen benutzen. Die eine Klasse an zwei Stellen benutzen wäre nicht dasselbe, weil das ja tatsächlich zweimal dasselbe Menü ausgibt - zwei Instanzen einer Klasse könnten sich ja aber innerlich unterscheiden.
Da ist also direkt die Frage: Worin unterscheiden sie sich? Inhaltlich, oder auch von der erzeugten HTML-Struktur? Beginnen wir mit "inhaltlich": Das Menü kriegt irgendwoher eine Liste von anzuzeigenden Linktexten. Wie kann man in PHP am einfachsten eine Liste mit Linktexten übergeben? Als Array!
Also tun wir das auch:
$links = array('Home', 'Impressum');
$navi = new Navi($links);
$navi->getHtmlMenu();
Wie sieht jetzt eigentlich der Code für die Klasse genauer aus?
class Navi
{
private $_links;
public function __construct(array $links)
{
$this->_links = $links;
}
public function getHtmlMenu()
{
$menustring = "";
foreach ($this->_links as $link) {
$menustring .= '<a href="">' . htmlspecialchars($link) . '</a>';
}
return $menustring;
}
}
In diese Klasse eine andere Liste von Links hineinzutun ist jetzt erstmal nicht sonderlich schwer. :)
Aber das andere Problem ist noch ungelöst: Wie kriegt man unterschiedlich aussehende, wohlmöglich unterschiedlich strukturierte Links hin? Das Grundelement für eine dynamische Linkliste ist eigentlich immer eine Kombination aus Linkziel und Linktext, aber wie dekoriert man da einen hübsch aussehenden HTML-Text drumherum? Mit dem Decorator-Pattern!
Das Decorator-Pattern nimmt ein eher "nacktes" Objekt und kapselt es ein, damit es nach außen besser aussieht oder auch eine Aufgabe angepasster ausführen kann.
Der hart kodierte HTML-Link-String dort oben ist also nicht sehr dynamisch, sollte aber genau das sein. Bauen wir also einen ganz simple Decorator, der erstmal genau dasselbe tut:
class Navi_Decorator
{
public function getHtmlLink($link)
{
return '<a href="">' . htmlspecialchars($link) . '</a>';
}
}
Wie kommt der Decorator ins Menü? Instantiieren und übergeben.
class Navi
{
private $_links;
private $_decorator;
public function __construct(array $links, Navi_Decorator $decorator)
{
$this->_links = $links;
$this->_decorator = $decorator;
}
public function getHtmlMenu()
{
$menustring = "";
foreach ($this->_links as $link) {
$menustring .= $this->_decorator->getHtmlLink($link);
}
return $menustring;
}
}
Aktuelle komplette Anwendung:
$links = array('Home', 'Impressum');
$decorator = new Navi_Decorator();
$navi = new Navi($links, $decorator);
$navi->getHtmlMenu();
Du siehst: Obwohl die Funktionalität sich außen kaum verändert, werden durch die inneren Komponenten, die man von außen mit hineintut, eventuell gewaltige Dinge bewegt. Deshalb noch als letztes die Sache mit den diversen Eigenschaften, die ein Link außer seinem Text hat: Linkziel und ggf. target.
Diese Dreierkombination passt eigentlich ziemlich gut ebenfalls wieder in eine Klasse:
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 sich Label und URL in den Linktext hineinpflanzen, dürfte leicht zu erkennen sein.
Wie man den HTML-Text des Links verändert, ist auch recht leicht: Man erbt von der existierenden Basis-Decorator-Klasse (aufgrund des Type-Hints im Konstruktor von "Navi" notwendig) und modifiziert den auszugebenden String.
An dieser Stelle existiert also ein Klassenkonstrukt aus zunächst drei Basisklassen, die sich schon um alle wesentlichen Aspekte eines Menüs kümmern können: Die Erzeugung des Gesamtmenüs steckt in "Navi", die Datenhaltung für einen einzelnen Link ist in "Navi_Link", und das Erscheinungsbild in "Navi_Decorator".
Weitere Schritte, die jetzt denkbar wären:
1. Wie kriegt man tatsächlich aus einer Datenbank eine Liste von Links erzeugt? Sowas ist jetzt eigentlich recht einfach, man kann z.B. mysqli_fetch_object("Navi_Link") aufrufen und erzeugt sich so ein passendes Link-Objekt, wenn man zuvor einen Query mit "label", "url" und "target" abgeschickt hat.
2. 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. Bei kleinen Ergebnissen ist das Kopieren in ein Array egal, bei großen Ergebnissen kostet es recht viel Speicher zusätzlich - denn PHP hält das DB-Ergebnis ja schon seinerseits in einem Puffer bereit. 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.
3. Das Dekorieren eines einzelnen Links ist schön und gut, aber damit allein kriegt man noch keine vollständige Linkliste hin - es fehlt ein Dekorator der gesamten Liste. Die einzelnen Links könnten so z.B. in "<li>" eingepackt werden, die gesamte Liste dann in ein "<ul>".
4. Die Dekoratoren immer mit festen Zeichenketten zu versehen ist irgendwann auch nicht mehr schön, man möchte sie vermutlich von außen konfigurieren können: Welches HTML-Element wird benutzt? Gibts Attribute? CSS-Klassen? Der in allen Dekoratoren gemeinsam genutzte Code wandert in die gemeinsame Elternklasse - Vererbung!
...
Mir würde vermutlich noch viel mehr einfallen, aber ich höre an dieser Stelle erst einmal auf.
- Sven Rautenberg