Kommunikation zwischen Objekten, Ressourcen-Sharing, Instanzierung und Zerstörung von Objekten, Datenbus-Emulation, Statusmeldung und -speicherung: Die Vielzahl der inneren Abläufe einer Anwendung lassen diese Abläufe einem Microkosmos gleichen. Unabhängig von der eigentlichen Funktion der Anwendung werden Nebenaufgaben ausgeführt, die, obgleich für den Anwender nicht sichtbar, für die reibungslose Funktion der Applikation unerlässlich sind. Je komplexer solch ein „Innenleben“ wird, desto länger braucht der Entwickler, einen Ablaufplan der Nebenaufgaben zu entwerfen. Viele Kleinigkeiten wollen bedacht und die Funktionsweise im Live-Betrieb oder durch Unit-Tests sichergestellt sein. Dies führt zwangsläufig zu einem langen Vorlauf vor der eigentlichen Implementation, der so genannten Konzeptionsphase. In dieser werden die Grundzüge und Abläufe der zu implementierenden Applikation entworfen. Grobe Fehler ziehen hier unweigerlich spätere Entwurfsrevisionen nach sich. Die Folgen sind nicht eingehaltene Zeitpläne und überschrittene Budgets. Dieser Artikel möchte Ihnen mit Entwurfsmustern (engl.: design patterns) einen Weg vorstellen, eine gewisse Planungssicherheit zu erlangen und grobe Entwurfsfehler von vornherein auszuschliessen.
Ein gutes Beispiel liefert die Automobil-Industrie, denn auch beim Autobau werden neue Modelle aus bereits konzeptionierten und für sich funktionalen Komponenten gefertigt. Müsste für jedes Modell der Motor und das Getriebe neu erfunden werden oder die Art und Weise, in der die Kupplung und das Getriebe zusammen arbeiten, so würde dies die benötigte Entwicklungszeit vervielfachen und die Entwicklungskosten explodieren lassen.
Doch zurück zu PHP, wo ebenfalls viele Applikationen bereits aus bestehenden Komponenten gefertigt werden. Kaum ein Programmierer macht sich heute beim Entwickeln noch die Mühe, eine neue Datenbankabstraktion oder einen neuen XML Parser zu schreiben ohne dass er dafür einen triftigen Grund hätte. Es gilt, den festgelegten und meist knappen Zeitplan einzuhalten. Da die Ganzheit der Anwendung und deren Funktionen im Vordergrund stehen, greift man auf bereits vorhandene Teillösungen der Problemstellung zurück und spart so Entwicklungszeit und -kosten ein.
Vorhandene Komponenten müssen allerdings noch verknüpft werden: Sie sollen miteinander funktionieren, sich ergänzen, vielleicht möglichst noch austauschbar sein falls später einmal eine Komponente verfügbar sein sollte, welche die gleichen Aufgaben besser und performanter erledigt. Oft erweist sich dieses Beziehungsgeflecht komplexer als die Implementierung an sich. Es gilt zu vermeiden, dass neu geknüpfte Beziehungen unerwartete Auswirkungen auf bereits funktionierenden und getesteten Code haben.
Maschen stricken
Hier kommen Entwurfsmuster auf den Plan. Entwurfsmuster beschreiben elegante Problemlösungen. Sie machen keine Aussage über die konkrete Implementierung einer Lösung, sondern dienen lediglich als (Entwurfs-) Schablone: Sie benennen, erläutern und bewerten Problemlösungen. Entwurfsmuster beschreiben die notwendigen Komponenten und ihr Zusammenspiel mit denen ein Problem bereits gelöst wurde. Sie stellen ein probates Mittel dar, sich das Wissen und die Erfahrung anderer nutzbar zu machen. Man kann sie (um wieder zur Automobil-Industrie zurückzukehren) als Studie verstehen, die das Funktionieren eines Gesamtkonzepts unter Beweis stellen, bei der endgültigen Realisierung aber Raum für Änderungen lassen. Vielleicht haben Sie schon einmal unwissentlich die Grundzüge des ein oder anderen Entwurfsmusters in Ihre Applikation einfliessen lassen. Tatsächlich ist es sogar unwahrscheinlich, dass eine größere Applikation nicht – zumindest teilweise – einer Idee zu Grunde liegt, welche in einem Muster bereits beschrieben wurde.
Bei Entwurfsmustern unterscheidet man heute objekt- und klassenbasierte Entwürfe (nicht jede Klasse repräsentiert alleine ein Objekt und nicht jedem Objekt liegt nur eine Klasse zu Grunde). Zusätzlich unterteilt man in Erzeugungs-, Struktur- und Verhaltensmuster. Ist die wesentliche Aufgabe eines Musters die Erzeugung von Exemplaren (Instanzen einer Klasse, auch Objekte der Klasse genannt), so spricht man von einem Erzeugungsmuster. Ein prominenter Vertreter der Erzeugungsmuster, das Singleton, wird im weiteren Verlauf dieses Artikels vorgestellt. Strukturmuster befassen sich überwiegend mit der Zusammensetzung von Klassen und deren Exemplaren und erweisen sich als adäquates Mittel, um beispielsweise größere Objekte auf kleinere ?herunterzubrechen? oder kleinere Objekte in einem Verbund als größeres Objekt erscheinen zu lassen (Maskierung der Objektgranularität). Die letzte Gruppe, die Verhaltensmuster, hingegen legt Zusammenspiele zwischen Klassen oder Objekten fest und teilt Zuständigkeiten zwischen Exemplaren auf. Die Einordnung eines Musters in eine der so definierten Kategorien erschliesst sich bei nähererer Betrachtung eines Entwurfs und wird für jedes Beispiel noch einmal näher erläutert werden.
Jede Vorstellung eines Entwurfsmuster wird nachfolgend aus vier Teilen bestehen: Sie soll helfen, das Muster wiedererzukennen wenn Sie darauf stoßen sollten, seine Grundzüge zu erfassen, diese anzuwenden und die daraus resultierenden Vor- und Nachteile zu erkennen:
- Die Bezeichnung dient sowohl zur Benennung eines Musters als auch dazu, auf einer höheren Ebene zu abstrahieren. Abstraktion schützt in diesem Falle davor, sich bereits während der Konzeptionsphase in den Details der Implementierung zu verlieren.
- Die Problembeschreibung zeigt ein Problem und sein Kontext auf. In der Problembeschreibung erkennen Sie vielleicht Ihr eigenes Problem wieder und sehen so, ob dieses Muster auf Ihr Problem anwendbar ist. Beigefügt finden Sie jeweils eine Liste mit Punkten, welche auf ein Problem zutreffen müssen, damit dieser Entwurf angewendet werden kann.
- Die Problemlösung ist der eigentliche Bauplan: Sie benennt alle Elemente eines Entwurfs sowie deren Verwendung und skizziert ein Beziehungsgeflecht. Sie gilt es auf die eigene Applikation zu adaptieren.
- Die finale Bewertung versucht zu guterletzt die kritische Betrachtung des Entwurfs und stellt dessen Vor-, aber auch dessen Nachteile heraus. Sie ist somit von zentraler Bedeutung für die Festlegung auf ein Muster während der Konzeptionsphase.
Ein steiniger Weg
Beginnend mit diesem Artikel startet eine Artikelserie, welche in jeder Ausgabe ein anderes Entwurfsmuster vorstellen wird. Anders als das Gros der diesem Thema gewidmeten Literatur werde ich mich auf Muster beschränken, die mit unserem eingesetzten Werkzeug sinnvoll realisierbar sind: mit PHP. Konkrete Beispiele für die Implementierung werden die einzelnen Teile der Serie ergänzen und sollen zum besseren Verständnis beitragen.
Grundvoraussetzung für das Verständnis der Artikel sind fortgeschrittene Kenntnisse der objekt-orientierten Programmierung. Sie sollten im Hinblick auf die Wiederverwendbarkeit Ihres Codes auf Schnittstellen und nicht auf Implementierungen hin programmieren, sonst werden Sie an der Lektüre dieses Artikes wenig Freude finden. Ich will Ihnen nichts vormachen: Der vor Ihnen liegende Weg ist steinig, doch das Ziel ist dafür umso lohnender. Die Zuordnung eines Musters zu einer Problemstellung ist nicht einfach, noch schwieriger ist die Adaption: Der Entwurf muss in Objekte zerlegt und diese zu Klassen geeigenter Granularität abstrahiert werden. Beziehungen wollen erkannt und Vererbungshierarchien definiert werden. Doch der Aufwand lohnt sich: Hat man es einmal geschafft, ein Muster anzuwenden, sind zukünfige Entwurfsrevisionen kaum notwendig. Gleichzeitig existiert ein Ablaufplan, welcher die Wartbarkeit des Codes, auch von an der Entwicklung unbeteiligter Personen, um ein Vielfaches erhöht. Ein Teil des Schemas, auf dem Ihre Applikation aufbaut, ist dann bereits ausformuliert und kann als nützliche Information der Entwickler-Dokumentation hinzugefügt werden.
Die Qual der Wahl
Jeder musterbasierte Entwurf beginnt mit der Auswahl des geeigneten Musters. Dieser Artikel macht es Ihnen noch leicht, da sich Ihr Repertoire an Mustern eventuell nur auf das eine hier vorgestellte Muster beschränkt. Doch mit jedem Teil der Serie wird Ihre Auwahl wachsen und die Entscheidung damit schwieriger werden. Meist gibt es kein ?richtiges? und kein ?falsches? Muster, viele Muster erfüllen sogar ähnliche Aufgaben. Folgende Tipps sollen Ihnen bei der Auswahl des für Ihre Aufgabe geeigneten Musters helfen:
Studieren Sie die Verwendungszwecke der Muster. Bei einer groben Vorauswahl hilft Ihnen eine Aufteilung der Muster in die bereits zuvor besprochenen Kategorien (Abb. 1). Scheinen zwei oder mehr Muster die gleiche Aufgabe zu erfüllen, wägen Sie die Vor- und Nachteile der Muster gegeneinander ab. Auch wenn ich ein wenig vorweg greife, indem die Abbildung Ihnen wahrscheinlich überwiegend unbekannte Entwurfsmuster aufzählt: Sie soll Ihnen lediglich bei einer Vorauswahl der Muster helfen, welche in Betracht kommen könnten. Einigen dieser Muster werden Sie in den folgenden Teilen dieser Serie begegnen.
Stellen Sie sich folgende Fragen: Wie lösen die in Frage kommenden Muster die Problematik: Welche Elemente werden benötigt und ist deren Objektgranularität mit Ihrer Applikation vereinbar?
Erkennen Sie, welche Teile Ihrer Applikation später vielleicht einmal erweitert oder sogar ganz ausgetauscht werden sollen. Überprüfen Sie, ob der von Ihnen favorisierte Entwurf dies zulässt. Ihr Augenmerk sollten Sie dabei vor allem auf die Kapselung der einzelnen Elemente legen: Sie entscheidet, welche Elemente später neu gruppiert werden können.
Rufen Sie sich abschliessend noch einmal ins Gedächtnis, wann Entwurfsmuster nicht angwendet werden sollten. Ist man nicht unbedingt auf die von einem Entwurfsmuster gebotene Flexibilität angwiesen oder ist die Performance ein absolut kritischer Faktor, macht es im Hinblick auf die Performance und die Komplexität des Entwurfs vielleicht sogar Sinn, auf Entwurfsmuster ganz zu verzichten (siehe „Flexibilität vs. Performance“).
Frisch ans Werk
Hat man sich einmal auf ein Muster festgelegt, gilt es dieses zu adaptieren. Vor der eigentlichen Adaption steht jedoch noch einmal das genaue Studium des Entwurfsmusters, um besser mit dessen Eigenheiten vertraut zu sein. Besonders die finale Abwägung der Vor- und Nachteile sollte von Interesse sein. Mit Ihrer Hilfe können Sie von Anfang an Problematiken erkennen, im günstigsten Falle sogar vermeiden. Überwiegen die aus dem Einsatz eines Musters resultierenden Nachteile die daraus erwachsenen Vorteile: Vergessen sie es! Sie müssen Entwurfsmuster nicht um jeden Preis einsetzen, nur um sie einzusetzen. Machen Sie sich stets klar, dass Muster lediglich eine Hilfe sein können, aber bei weiten kein Muss darstellen. Ist Ihre Applikation jedoch so konzipiert, dass dass sie aus dem ausgwählten Entwurfsmuster einen echten Nutzen ziehen kann, sind Sie auf dem richtigen Weg. Erst wenn Sie von den Vorteilen überzeugt sind, sollten Sie mit der Adaption beginnen. Zunächst definieren Sie anhand der vom Entwurf benannten Elemente die dazu benötigten Klassen. Definieren Sie für jede Klasse ihre Schnittstellen und Klassenvariablen sowie ihre Vererbungshierarchien (engl.: inheritance). Bauen Sie nun das Beziehungsgeflecht auf und überprüfen Sie wieder und wieder, ob die von Ihnen bereits definierten Schnittstellen die vorgesehenen Beziehungen ermöglichen oder ob die Methoden-Signaturen (engl.: method signatures: Summe der Methoden-Parameter und Rückgabewerten) noch einmal modifiziert werden müssen.
Funktioniert das von Ihnen auf Basis eines Entwurfsmusters geknüpfte Beziehungsgeflecht vollständig in der Theorie, ist es an der Zeit, mit der eigentlichen Implementierung zu beginnen. Achten Sie hierbei auf eine konsistente und griffige Namensgebung der Objekte, Klassen und Variablen. Sie hilft Ihnen später das Zusammenspiel der Komponenten besser zu verstehen und macht den Code gleichzeitig übersichtlicher und bis zu einem gewissen Grad vielleicht sogar selbsterklärend. Behalten Sie stets im Hinterkopf, dass es bei der Implementierung nur darauf ankommt, die entworfene Funktion einer Schnittstelle wie vorgesehen zu verwirklichen. Nutzen Sie keinesfalls Ihr Wissen über die konkrete Implementierung, wenn Sie ein Objekt ansprechen. Schreiben Sie stets nur soviel Code wie Sie benötigen, dessen Funktion sicherzustellen und durch Methoden-Parameter die korrekten Rückgabewerte zu erzeugen. Abschließende Unit-Tests stellen sicher, dass Ihr Code den Ansprüchen des Entwurfs genügt. Sollten Sie mit den Gründzügen des eXtreme Programming vertraut sein, werden Sie vielleicht viele der dort verankerten Ideen in diesem Abschnitt wiedererkannt haben. Aber sollten Ihnen die vorangegangenen Sätze über die Implementierung an sich nicht ausführlich genug gewesen sein (was sich im Rahmen dieses Grundlagenartikels leider nicht vermeiden lässt), so finden Sie in Werken über eXtreme Programming weiterführende Literatur, die im Geiste dieses Artikels verfasst wurde.
Sämtliche vorangegangenen Aussagen sind als Ratschläge zu verstehen. Die ?perfekte Herangehensweise? zur Anwendung von Entwurfsmustern gibt es nicht. Ich hoffe lediglich, Sie für die relevanten Aspekte dieses Themenfeldes interessiert und sensibilisiert zu haben. Am Ende dieses Artikels finden Sie Verweise auf weiterführende Literatur, die für mich persönlich zu meinem Verständnis der Entwurfsmuster beigetragen hat. Die meiste Erfahrung sammelt man jedoch wie sooft in der Praxis. Geben Sie nicht gleich frustriert auf, wenn Ihnen die Adaption eines Musters nicht gleich beim ersten Anlauf gelingt: Im Laufe der Zeit werden Sie merken, wie sich Ihr Auge weiter schult und Ihnen Ansätze von Mustern schneller auffallen. Es wird Ihnen zunehmend leichter fallen, Entwurfsmuster für Ihre Zwecke einzusetzen.
Zur praktischen Übung und zum besseren Verständnis stelle ich Ihnen ein erstes Entwurfsmuster vor: Das Singleton. Die Tatsache, dass das Singleton recht einfach aufgebaut sowie schnell und häufig einsetzbar ist, macht es zu einem idealen Einstieg in den musterbasierten objekt-orientierten Entwurf.
Terminologie
Oft ist zu beobachten, dass in deutschen Publikationen über Entwurfsmuster stellenweise, wenn nicht sogar ausschliesslich, auf die englischen Ausdrücke zurückgegriffen wird. Obwohl es im Deutschen durchaus gängige und sinngemäß übersetzte Termini gibt haben sich die Autoren jener Publikationen für die englischen ursprünglichen Ausdruck entschieden. Dieser Artikel macht ausschließlich von der deutschen Fachterminologie Gebrauch, welche Verwechslungen und Missverständnissen vorbeugt.
Objekte sind erzeugte Instanzen einer Klasse, auch Exemplar der Klasse genannt. Objekt-interne Variablen sind somit Exemplarvariablen. Klassen können Attribute und Methoden von Oberklassen (engl.: parents) erben oder an Unterklassen (engl: childrens) vererben. Dient eine Klasse nur als Schnittstelle für ihre Unterklassen, ist sie eine abstrakte Klasse: Ihre deklarierten aber nicht implementierten Methoden sind abstrakte Methoden. Nicht abstrakte Klassen sind konkrete Klassen. Klassenvererbung (engl.: inheritance) dient dazu, die Implementierung einer Klasse durch eine andere Klasse zu vereinfachen. Schnittstellenvererbung dient der Austauschbarkeit von Objekten dank gleicher Schnittstellen.
Die Schnittstellen einer Klasse sind die Summe aller Methoden-Signaturen dieser Klasse. Methoden-Signaturen sind die Summe aller Parameter und Rückgabewerte einer Methode.
Flexibilität vs. Performance
Man sollte sich stets bewusst sein, dass Entwurfsmuster zu den fortgeschritteneren Techniken objekt-orientierter Programmierung zählen. Sie realisieren die Flexibilität und Variabilität meist durch Einführung einer zusätzlichen Zwischenebene, der so genannten Interdirektionsebene. Die Aufgabe dieser Ebene ist es, zwischen den einzelnen Elementen eines Entwurfs zu vermitteln. Da dies Befehlswege umleitet und somit zwangsläufig verlängert, erhöht sich gleichzeitig die Komplexität des Entwurfs. Wenn einfache Applikationen darauf nicht unbedingt auf Entwurfs-Flexibilität angewiesen sind, können die Abläufe durch einen Verzicht auf Entwurfsmuster einfacher gehalten werden. Ein weiterer unschöner Nebeneffekt der Interdirektionsebene: Mit dem benötigten zusätzlichen Programmcode wächst auch die Zahl der zu verarbeitenden Instruktionen. Gerade in erst zur Laufzeit übersetzten Skriptsprachen wie PHP ein stellt dies einen nicht zu unterschätzenden Faktor dar, der sich negativ auf die Performance auswirken kann.
Das Singleton
Problembeschreibung
Oft ist es wichtig oder zumindest von Vorteil, wenn es von einer Klasse genau ein Exemplar gibt. Die Gründe hierfür können vielschichtig sein. Denkt man etwa an eine Klasse, die Session-IDs generiert, verwaltet und vergibt, so ist es essenziell wichtig, dass nur ein Exemplar dieser Klasse existiert.
Ein weiterer, oft genutzter Einsatzzweck ist das Ressourcen-Sharing. Verwendet man beispielsweise eine Klasse zur Logfile-Generierung, vermeidet man I/O Konflikte, indem das Logfile von genau einem Exemplar dieser Klasse verwaltet wird, auf das von verschiedenen Stellen im Code aus zugegriffen wird. Auch können limitierte Ressourcen, etwa die Anzahl möglicher Verbindungen zu einer Datenbank, auf stark frequentierten Seiten schon einmal knapp werden. Hat man nicht die Möglichkeit, die Anzahl der erlaubten Verbindungen selbst zu erhöhen, muss man Massnahmen ergreifen, die knappen Ressourcen so gut wie möglich zu schonen. Ein Ansatzpunkt ist die Datenbankabstraktion, sofern eine eingesetzt wird. Diese ist meist als Objekt realisiert und benötigt mindestens eine Verbindung zur Datenbank. Verwendet eine Applikation nun mehrere Exemplare der Datenbankabstraktion, erhöht sich damit automatisch die Anzahl der Datenbank-Verbindungen. Überlässt man hingegen dem Singleton-Muster (engl: singleton pattern) die Verwaltung der Datenbankabstraktion, benötigt jeder Benutzer nur noch eine Datenbank-Verbindung. Kombiniert man dies noch mit SRM (Script Running Magic), ist es durchaus möglich, dass alle Benutzer sich genau eine Datenbankverbindung teilen. Doch dies sei nur als Idee gegeben, was durch die Kombination von Entwurfsmustern und Extensions möglich ist.
Problemlösung
Das Prinzip hinter dem Singleton lässt sich mit wenigen Worten beschreiben: Man nutzt nicht mehr das Sprachkonstrukt new um ein Exemplar einer Klasse zu instanzieren, man überlässt dies dem Singleton. Diesem übergibt man lediglich den Namen der zu instanzierenden Klasse. Das Singleton prüft nun, ob bereits ein Exemplar dieser Klasse existiert. Ist dies der Fall, wird nur eine Referenz auf das Exemplar zurückgeliefert. Existiert noch kein Exemplar, wird dieses erst noch instanziert, bevor eine Referenz darauf zurückgegeben wird. Das Singleton wird somit der zentrale Zugriffspunkt auf Objekte, für die nur ein Exemplar existieren darf.
Das Singleton ist somit der Kategorie der Erzeugungsmuster zuzuordnen, da eine seiner Aufgaben darin besteht, neue Exemplare von Klassen zu instanzieren. Obwohl es auch Referenzimplementierungen als einfache Funktion gibt, ist das Singleton dem Lehrbuch nach ein objektbasiertes Muster. Unsere Beispiel-Implementierung ist hierbei noch ein Hybrid aus Funktion und Klasse, dazu aber mehr im folgenden Abschnitt.
Implementierung
In PHP existieren zahllose Implementierungen des Singleton-Musters. Einige sind sehr einfach gehalten, während andere mächtig aber komplex realisiert sind. Die hier vorgestellte Beispiel-Implementierung ist so einfach wie möglich gehalten, birgt aber eine Besonderheit: Die Aufteilung des Entwurfsmusters in eine Funktion und eine Klasse. Implementierungen, die nur aus einer Funktion bestehen, mangelt es oft an Flexibilität. Rein objekt-orientierte Entwürfe werden schnell komplex, sobald Sie überwachen, dass auch das Singleton mit nur einem Exemplar vertreten ist.
Doch ist es eine Notwendigkeit, dass nicht mehrere Exemplare des Singletons existieren, sofern jedes Exemplar die übertragenen Klassen für sich verwaltet. Weiss ein Singleton nichts von einem zweiten Singleton, werden vielleicht doch wieder zwei Exemplare einer Klasse instanziert, von der nur ein Exemplar vorgesehen war.
Wir lösen dies recht einfach: Unsere Singleton Klasse wird nicht direkt, sondern durch eine Funktion Singleton angesprochen (Listing [1]). Diese Funktion stellt durch eine statische Variable sicher, dass es nur ein Exemplar des Singletons gibt. Das Singleton selbst ist als Klasse realisiert (Listing [2]). Diese Klasse hat ein Klassen-Array zur Speicherung aller existenten Exemplare der ihr übertragenen Klassen. Die Methode singleton::instance($class) prüft bei Aufruf, ob bereits ein Exemplar der Klasse $class in diesem Array vorliegt, instanziert dies im Bedarfsfalle und liefert eine Referenz darauf zurück.
Listing [3] zeigt ein einfaches Anwendungsbeispiel mit der Klasse test. Diese Klasse hat als einzige Aufgabe die Speicherung der Microtime zum Zeitpunkt der Instanzierung. Die Methode test::output() gibt die Microtime zur Kontrolle aus. Der Sinn dieses Tests ist der Nachweis, dass es unabhängig von der Zahl der Aufrufe genau ein Exemplar der Klasse test gibt, sofern diese durch das Singleton verwaltet wird. Zweimal lassen wir uns vom Singleton ein Exemplar der Klasse test zurückliefern und geben die Microtime aus, wann dieses Exemplar instanziert wurde.
Zwischen den beiden Aufrufen lassen wir etwas Zeit verstreichen um eine andere Microtime zum Zeitpunkt einer eventuellen Instanzierung haben. Beim Aufruf beobachten wir jedoch, dass zweimal die gleiche Microtime ausgegeben wird. Daraus können wir folgern, dass nur eine Instanzierung stattgefunden hat und die Microtime in beiden Fällen vom gleichen und einzigen Exemplar der Klasse test zurückgeliefert wurde. Unser Singleton funktioniert damit wie erwartet.
Bewertung
Die hier vorgestellte und sehr einfach gehaltene Implementierung des Singleton Entwurfsmusters besitzt mehrere Vorteile, birgt aber gerade wegen ihrer Einfachheit auch einige Nachteile. Ohne Zweifel von Vorteil ist die Exemplarverwaltung: Sie lässt nur genau ein Exemplar einer Klasse zu, kann aber auch mit geringem Aufwand so modifiziert werden, dass eine andere festgelegte Zahl von Exemplaren möglich ist. Ab PHP5 lässt sich das Exemplarspeicher-Array des Singletons via protected so Kapseln, dass von außen kein Zugriff mehr möglich ist, dies bietet einen zusätzlichen Schutz. Gleichzeitig bewirkt eine Speicherung der Exemplare in diesem Klassen-Array, dass der globale Namensraum nicht weiter belegt wird.
Allerdings dürfen die Nachteile, die aus der Einfachheit der hier vorgestellten Implementierung erwachsen, nicht verschwiegen werden: So ist es in unserem Beispiel nicht möglich, dem Konstruktor der zu instanzierenden Klasse eine variable Anzahl von Parametern zu übergeben. Dies kann allerdings durch einige Modifikationen nachträglich ermöglicht werden. Eine weitere Einschränkung entsteht dadaurch, dass das Exemplar im Singleton durch den Namen seiner Klasse repräsentiert wird. Ist diese Klasse lediglich eine abstrakte, polymorphe Schnittstelle, führt dies zu Problemen. Mehrere Exemplare dieser Klasse (mit jeweils unterschiedlichen Aufgaben) lassen sich nur durch Vererbung auf eine Unterklasse realisieren. In diesem Falle überträgt man dem Singleton nur die Verwaltung der Unterklasse.
Insgesamt gesehen ist das Singleton ein einfach zu adaptierendes Entwufsmuster, dessen Mächtigkeit auch aus der Tatsache erwächst, dass es recht häufig anwendbar ist. Dies hat zu seiner weiten Verbreitung beigetragen, die das Singleton-Muster zu einem der bekanntesten Entwurfsmuster macht.
Weiterführende Literatur
- Gamma, Helm, Johnson, Vlissides: Entwurfsmuster (Addison-Wesley)
- Beck: Extreme Programming, Das Manifest (Addison-Wesley)
- ::phpPatterns()
Listing [1]: Die Singleton-Funktion
function &singleton($class) { static $singleton; if (!is_object($singleton)) { $singleton = new singleton(); } return $singleton->instance($class); }
Listing [2]: Die Singleton-Klasse
class singleton { var $instances = array(); function singleton() { } function instance($class) { if(!is_object($this->instances[$class])) { $this->instances[$class] = new $class(); } $pointer =& $this->instances[$class]; return $pointer; } }
Listing [3]: Anwendungsbeispiel und die Klasse Test
class test { var $microtime = NULL; function test() { $this->microtime = microtime(); } function output() { echo $this->microtime,"\n"; } } $test = singleton('test'); $test->output(); sleep(1); $test = singleton('test'); $test->output();