OO-Design & EntwurfsmusterAuf dieser Seite… (ausblenden)
1. Motivation1.1 Warum OOP?Wir wollen uns zunächst fragen, warum wir eine PHP-Anwendung überhaupt objektorientiert entwerfen wollen und nicht den vermeintlich einfacheren Weg der prozeduralen Programmierung wählen. Für Code, der für die Verwendung durch andere Entwickler bestimmt ist oder von mehreren Programmierern erstellt wird, sollte vor dem eigentlichen Schreiben zunächst ein Design erstellt werden. Der Sinn eines solchen Designs ist es, gut organisierten und konsistenten Code zu schreiben, der einfach zu erweitern und zu warten ist. Ende der 60er, Anfang der 70er Jahre des vergangenen Jahrhunderts hielten neue Sprachkonstrukte, und mit ihnen ein neues Programmierparadigma, Einzug in Programmiersprachen wie Simula oder Smalltalk. Sie sollten die Formulierung des Designs in der Programmiersprache gegenüber dem etablierten prozeduralen Ansatz erleichtern. So leistet der prozedurale Code in dem Beispiel unten zwar die von ihm erwartete Aufgabe, ist aber in mehrerlei Hinsicht "unschön". An eine Wiederverwendung des Codes im eigentlichen Sinne von "einmal schreiben, mehrfach verwenden" ist allerdings nicht zu denken. Höchstens durch Duplizierung und Anpassung an den neuen Kontext kann er an anderer Stelle eingesetzt werden. Beispiel: Zugriff auf eine MySQL-Datenbank ohne objektorientierte Konzepte
Wünschenswert wäre eine wiederverwendbare Einheit, die die Datenbankverbindung und die mit ihr assoziierten Operationen zusammenfasst. Ist sie einmal mit den Verbindungsparametern initialisiert, kümmert sich diese Einheit für ihren Verwender unsichtbar um buchhalterische Aufgaben wie Verbindungsauf- und -abbau, Verarbeitung von Anfragen und dergleichen mehr. Um eine solche Einheit programmiertechnisch umsetzen zu können, bedarf es eines zusätzlichen Sichtbarkeitsbereiches (englisch: scope) neben dem von Hauptprogramm und Prozedur. Dieser neue Sichtbarkeitsbereich soll Variablen und Prozeduren, die diese Variablen manipulieren, in einer Einheit zusammenfassen. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Motivation. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.motivation.html [01.03.2008, 16:30 GMT+1]) Eine Klasse für den Zugriff auf eine MySQL-Datenbank
Verwendung der MySQL-Klasse
Bei der Deklaration von Methoden erlaubt PHP die Angabe eines Klassen- oder Schnittstellennamens für als Parameter übergebene Objekte. Im Gegensatz zu statisch getypten Programmiersprachen erfolgt die Typprüfung jedoch nicht zum Zeitpunkt der Kompilierung, sondern erst zur Laufzeit. Diese so genannten Type Hints ersparen dem Programmierer Schreibarbeit, wie an folgenden Beispielen zu erkennen ist. Typprüfung mit Type Hints
Typprüfung mit dem instanceof-Operator
Die Beispiele sind semantisch äquivalent und unterscheiden sich nur in der Art der Typprüfung durch die Type Hints beziehungsweise den instanceof-Operator. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Klassen und Objekte. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.classes-objects.html [01.03.2008, 16:30 GMT+1]) 2. OOP in PHP2.1 PolymorphieMit dem Prinzip der Polymorphie (Vielgestaltigkeit) wird ein dynamisches Verhalten von Methoden verfolgt, das von Anzahl und Typ der übergebenen Parameter abhängt. Im Falle einer dynamisch getypten Programmiersprache wie PHP gestaltet sich dies jedoch anders als in einer statisch getypten Programmiersprache wie beispielsweise Java. Für unterschiedliche Anzahl oder Typen der erwarteten Parameter einer Methode schreibt man beispielsweise in Java mehrere Methoden mit dem gleichen Namen. Beispiel: Polymorphie in Java In PHP ist die Deklaration von mehreren Methoden des gleichen Namens nicht vorgesehen. Polymorphes Verhalten von Methoden kann in PHP auf eine der folgenden Arten erreicht werden: Eine Methode kann wegen der dynamischen Typisierung einen Parameter akzeptieren, der unterschiedliche Typen enthalten darf. Eine Methode kann eine variable Anzahl an Parametern akzeptieren, indem optionale Parameter mit Standardwerten versehen und an das Ende der Parameterliste gesetzt werden. Häufig verwendet man ein assoziatives Array als einzigen Parameter einer Methode. Dies ermöglicht eine variable Anzahl an (benannten) Parametern für die Methode, deren Reihenfolge aufgrund der Assoziativität beliebig sein kann. Beispiel: Polymorphie in PHP (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Polymorphie. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.polymorphism.html [01.03.2008, 16:30 GMT+1]) 2.2 Serialisierung von ObjektenObjekte existieren nur zur Laufzeit, können aber durch die Speicherung ihres Zustandes persistent (dauerhaft) gemacht werden. Sie können somit gespeichert und zu einem späteren Zeitpunkt wieder geladen werden. Die Speicherung eines Objektes und seines Zustands wird auch Serialisierung genannt. PHP bietet die Funktionen serialize() und unserialize(), um ein Objekt zu serialisieren beziehungsweise aus der serialisierten Form wieder ein Objekt zu erstellen. Hierbei erzeugt serialize() aus einem Objekt einen String, in dem die relevanten Daten kodiert sind. Dieser String kann dann beispielsweise in eine Datei geschrieben oder in einer Datenbank abgelegt werden. Analog erwartet unserialize() einen String in diesem Format, um ein Objekt aus dem kodierten String wiederherzustellen. Verfügt die Klasse des Objektes, das serialisiert werden soll, über eine __sleep-Methode, so wird diese automatisch vor der eigentlichen Serialisierung auf dem Objekt aufgerufen. Diese Methode muss ein Array mit den Namen derjenigen Instanzvariablen zurückliefern, die serialisiert werden sollen. So kann die __sleep-Methode einer Klasse, die eine Datenbankverbindung kapselt, beispielsweise Sorge dafür tragen, dass nur die für den Verbindungsaufbau nötigen Parameter gespeichert werden, nicht aber die Ressource-ID der aktuell bestehenden Verbindung. Verfügt die Klasse des Objektes, das deserialisiert werden soll, über eine __wakeup-Methode, so wird diese automatisch nach der eigentlichen Deserialisierung auf dem Objekt aufgerufen. Wird ein Objekt durch Ablegen in dem Array $_SESSION[] als Session-Variable registriert, so kümmert sich PHP automatisch um Serialisierung und Deserialisierung des Objektes zwischen den einzelnen Requests der Session. Beispiel: Verwendung der Methoden __sleep() und __wakeup()
Weiterhin ist hier das Konzept Object-relational mapping (ORM) zu erwähnen, welches die Speichern von Objekten in einer relationalen Datenbank ermöglicht. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Serialisierung von Objekten. Online im Internet: URL: [01.03.2008, 16:30 GMT+1]) 2.3 Die Reflection APIDie strukturelle Reflexion, auch Introspektion genannt, macht die Struktur eines objektorientierten Systems durch die Programmiersprache des Systems zugänglich. Dies ermöglicht das Schreiben von generischem Code, der zur Laufzeit Informationen über Klassen und deren Objekte abfragt und diese entsprechend verarbeitet. Diese Generizität wird beispielsweise für das Schreiben von Code- und Dokumentationsgeneratoren benötigt. Ferner bildet sie die Grundlage für Werkzeuge wie PHPUnit, die "fremden" Code analysieren und ausführen müssen.
Allen Klassen der Reflection API gemein ist die statische Methode export(). Diese liefert eine textuelle Darstellung des reflektierten Sprachobjekts. Beispiel: Verwendung von ReflectionClass::export() Class [ <user> class Klasse ] { @@ /home/sb/export.php 2-11 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [3] { Property [ <default> public $public ] Property [ <default> protected $protected ] Property [ <default> private $privateStatic ] } - Methods [1] { Method [ <user> public method methode ] { @@ /home/sb/export.php 7 - 10 - Parameters [1] { Parameter #0 [ <required> Klasse $objekt ] } } } } Stellvertretend für die Klassen der Reflection API betrachten wir die Klasse
Im nächsten Beispiel erzeugen wir zunächst ein Objekt der Klasse Beispiel: ReflectionClass und ReflectionMethod im Einsatz
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Die Reflection API. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.reflection-api.html [01.03.2008, 16:30 GMT+1]) 2.4 Migration von PHP 4 zu PHP 5Bei der Entwicklung von PHP 5 wurde versucht, die Abwärtskompatibilität zu PHP 4 zu wahren. In diesem Abschnitt finden Sie eine Übersicht über Änderungen in PHP 5, die eine Änderung von bestehenden PHP-4-Anwendungen erforderlich macht. Kopie versus Referenz Mit der Einführung des neuen Objektmodells werden Objekte standardmäßig per Referenz übergeben. In PHP 4 wurde stattdessen stets eine Kopie übergeben. Für PHP-Programme, die wie von PHP 4 gewohnt eine Kopie statt einer Referenz erwarten, kann die php.ini-Direktive zend.ze1_compatibility_mode auf On gesetzt werden. Konstruktor In PHP 4 entsprach der Name des Konstruktors dem Namen der Klasse. In PHP 5 heißt der Konstruktor nun __construct. Wird in einer Klasse keine Methode mit dem Namen __construct deklariert, so wird nach einer Methode gesucht, die den Namen der Klasse trägt. Wird eine solche Methode gefunden, so wird sie als Konstruktor benutzt. Wird eine Methode mit dem Namen __construct gefunden, so wird diese in jedem Fall (unabhängig davon, ob auch eine Methode mit dem Namen der Klasse existiert) benutzt. Klassendeklaration vor Objekterzeugung In PHP 4 war es möglich, ein Objekt einer Klasse zu erzeugen, die zum Zeitpunkt der Instanzierung noch nicht deklariert war. In PHP 5 ist dies nicht mehr möglich, wenn die Klasse Sprachmerkmale verwendet, die mit PHP 5 eingeführt wurden. Klassen, die nur Sprachmerkmale enthalten, die bereits in PHP 4 zur Verfügung standen, können weiterhin vor ihrer Deklarierung verwendet werden. Neue Schlüsselwörter In PHP 5 sind eine Reihe von neuen Schlüsselwörtern hinzugekommen, die nicht mehr als Namen von Klassen, Konstanten, Methoden oder Funktionen verwendet werden können:
Bei der Migration von PHP 4 nach PHP 5 müssen Klassen, Konstanten, Methoden oder Funktionen, die einen dieser Namen tragen, umbenannt werden. Besondere Methoden In PHP 5 sind einige Methodennamen hinzugekommen, die mit einer besonderen Semantik verknüpft sind:
Bei der Migration von PHP 4 nach PHP 5 müssen Methoden, die einen dieser Namen tragen, umbenannt werden. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Migration von PHP4 zu PHP5. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.migration.html [01.03.2008, 16:30 GMT+1]) 2.5 InterzeptionsmethodenNeben Konstruktor und Destruktor sowie den Methoden PHP bietet die folgenden Interzeptormethoden an. Sie werden automatisch aufgerufen beim Zugriff auf nicht deklarierte Instanzvariablen und Methoden eines Objektes, beim Versuch, ein Objekt einer nicht deklarierten Klasse zu erzeugen, sowie bei der Typumwandlung eines Objektes in einen String.
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Interzeptionsmethoden. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.interceptors.html [01.03.2008, 16:30 GMT+1]) 3. EntwurfsprinzipienDie OO-Basics (Abstraktion, Kapselung, Polymorphismus, Vererbung) machen nicht allein ein gutes OO-Design. Gute Entwürfe sind wiederverwendbar, erweiterbar und wartbar.
Während der letzten Jahre haben sich in der objektorientierten Programmierung einige sehr nützliche Entwurfsmuster, englisch Design Patterns, herausgebildet. Entwurfsmuster dienen dazu, gewonnene Erfahrungen über die Lösung wiederkehrender Probleme zu vermitteln. Diese Kombination von Problem und Lösung wird zudem mit einem prägnanten Namen versehen, um ein einheitliches Vokabular zur Diskussion von Problem und Lösung zur Verfügung zu stellen. Die Vorteile der Entwurfsmuster liegen auf der Hand: Durch die Nutzung von vorhandenem Wissen spart man Zeit bei der Entwicklung und kann Fehler vermeiden, die bereits von anderen gemacht wurden. Die Aufgabe des Softwareentwicklers verlagert sich unter Verwendung von Entwurfsmustern von der Erfindung des Rads zur Auswahl des richtigen Rads und seiner kreativen Verwendung. Hierbei sollte man jedoch die folgenden Punkte stets im Hinterkopf behalten:
Originalität ist nach wie vor bei der Anwendung der Entwurfsmuster gefragt.
Algorithmen lösen feinkörnigere Probleme (Suchen, Sortieren) und bieten weniger Freiheitsgrade in der Implementierung.
Frameworks existieren als konkreter, wiederverwendbarer Code, Entwurfsmuster enthalten nur Beispiele von Code. Frameworks werden für konkrete Anwendungsbereiche eingesetzt, ein Entwurfsmuster kann überall eingesetzt werden.
Es gibt drei Gruppen von Entwurfsmustern:
4. Design-Pattern(siehe auch: Entwurfsmuster) 4.1 Das Strategy-MusterDas Strategy Pattern definiert eine Familie von Algorithmen, kapselt sie einzeln und macht sie austauschbar. Das Strategy-Muster ermöglicht es, den Algorithmus unabhängig von den Clients die ihn einsetzen, variieren zu lassen. Reine Vererbung ist zwar im Hinblick auf Wiederverwendbarkeit gut, jedoch schlecht im Hinblick auf Wartbarkeit. Leicht kann es zu Unklarheiten in der Vererbungshierarchie kommen, falls Neues hinzugefügt wird. Interface Implementierungen wiederum machen es zu jeder Zeit möglich spezielles Verhalten zu definieren und der Typhierarchie hinzuzufügen, jedoch geschieht dies auf Kosten von Code-Redundanz und Code-Duplizierung, was zur Folge hat, dass der Code schlecht zu warten ist. Durch das Strategy-Pattern, welches ein Verhaltensmuster ist, werden veränderbare Teile aus den konstanten Teilen herausgezogen und Interfaces (Supertyp) definiert. Die Klassen der Verhaltensweisen implementieren die Interfaces. Andere Klassen können nun diese Verhaltensweisen in sich aufnehmen und in Form einer Komposition delegieren. Getter- und Setter-Methoden in diesen KLassen machen den dynamischen Austausch zur Laufzeit möglich. Dadurch sind Verhaltensweisen leicht austauschbar und wiederverwendbar. Der resultierende Code ist gut wiederverwendbar und wartbar. Demnach erfüllt das Strategy-Muster die drei oben aufgeführten Entwurfsprinzipien. Bsp:
–– Problem Eine Familie von Algorithmen soll gekapselt werden, mit der Möglichkeit, sie beliebig auszutauschen. Motivation Das Strategie-Muster bietet sich immer dann an, wenn eine Aufgabe mit unterschiedlichen Verfahren, die sich beispielsweise in Geschwindigkeit und Speicherverbrauch unterscheiden, zu lösen ist. Der Eingabe entsprechend kann so das jeweils beste Verfahren dynamisch zur Laufzeit verwendet werden. Es kann ebenfalls genutzt werden, wenn verwandte Klassen sich nur in ihrem Verhalten unterscheiden oder eine Klasse unterschiedliche Verhaltensweisen definiert und diese mit unübersichtlichen if-then-else- oder switch-case-Konstruktionen in ihren Methoden implementiert sind. Zusammenhängende Zweige dieser Bedingungsanweisungen können übersichtlich in eigene Strategieklassen ausgelagert werden. Lösung Die Verwandtschaft der Klassen wird durch Implementieren einer gemeinsamen Schnittstelle zum Ausdruck gebracht. Die Verwenderklasse arbeitet mit einem Objekt einer Klasse, die diese Schnittstelle bereitstellt. Dieses Objekt kann zur Laufzeit durch eine Methode setStrategy($strategy) ausgetauscht werden, wodurch das Verhalten der Verwenderklasse dynamisch geändert werden kann. Anwendungsbeispiele Das folgende Beispiel zeigt eine Implementierung des bekannten Sortierverfahrens Bubble-Sort. In der hier gezeigten Variante lässt sich der Vergleich von zwei Elementen der zu sortierenden Menge durch Verwendung einer Strategie austauschen. Beispiel: Die Schnittstelle CompareStrategy
Beispiel: Die Klasse AscendingCompare
Beispiel: Die Klasse DescendingCompare
Beispiel: Die Klasse BubbleSort
Array ( [0] => 4 [1] => 22 [2] => 1978 ) Array ( [0] => 1978 [1] => 22 [2] => 4 ) In seiner Zielrichtung ist das Strategie-Muster mit der Schablonenmethode verwandt. Der Unterschied zwischen diesen beiden Mustern liegt in der Wahl des Mittels, mit dem man das Ziel zu erreichen versucht: Während das Strategie-Muster mittels Delegation den gesamten Algorithmus zur Laufzeit austauschbar macht, nutzt das Muster der Schablonenmethode Vererbung, um einzelne Schritte einer Operation variabel zu gestalten. (Quelle: Strategy-Muster, Sebastian Bergmann, 01.03.2008, 16:30 GMT+1) 4.2 Das Observer-MusterProblem Wenn ein Objekt seinen Zustand ändert, sollen davon abhängige Objekte benachrichtigt werden. Motivation Eine Änderung an einem Objekt erfordert Änderungen an anderen Objekten, um die Konsistenz des Gesamtsystems zu erhalten. An Stelle einer engen Kopplung wird eine lose Kopplung der Objekte angestrebt, bei der die beteiligten Objekte unabhängig voneinander variiert werden können. Lösung Das unter Beobachtung stehende Objekt, Subject genannt, stellt die folgenden Methoden zur Verfügung, über die sich andere Objekte, Observer genannt, für die Benachrichtigung bei Zustandsänderungen an- und abmelden können:
Registriert das Objekt $observer zur Benachrichtigung bei Änderung des Zustands des Objektes, dessen attach()-Methode aufgerufen wird.
Hebt die Registrierung des Objektes $observer zur Benachrichtigung bei Änderung des Zustands des Objektes, dessen
Liefert den aktuellen Zustand des Objektes. Ein Beobachter-Objekt kann sich so nach der Benachrichtigung über eine Änderung des Zustands des beobachteten Objektes darüber informieren, wie sich dessen Zustand geändert hat. Die Benachrichtigung der Beobachter-Objekte erfolgt durch den Aufruf der Methode Die folgende Abbildung zeigt zwei Klassen Subject und Observer, die die beschriebene Funktionalität zur Verfügung stellen und als Basis für zwei konkrete Klassen, ConcreteSubject und ConcreteObserver, dienen. Abbildung: Struktur des Beobachter-Musters Beispiel: Die abstrakte Klasse Subject
Beispiel: Die abstrakte Klasse Observer
Beispiel: Die Klasse ConcreteSubject
Beispiel: Die Klasse ConcreteObserver
Beispiel: Verwendung von Subject und Observer
Anwendungsbeispiele Das Problem, das ursprünglich das Beobachter-Muster motivierte, war die Kommunikation zwischen dem Model-Objekt und dessen View-Objekten in einer Applikation, die dem Model-View-Controller-Prinzip (MVC) folgt. Hierbei wird eine Anwendung in die drei Schichten Datenmodell (Model), Darstellungsschicht (View) und Steuerungsschicht (Controller) unterteilt. Diese Schichten sind voneinander entkoppelt: Das Datenmodell kennt weder Darstellungsschicht noch Steuerungsschicht. Die Darstellungsschicht registriert sich als Beobachter des Datenmodells und stellt dieses dar. Die Steuerungsschicht steuert den Ablauf der Anwendung und nimmt Änderungen am Datenmodell über dessen Programmierschnittstelle vor. Ein anderes Einsatzgebiet des Beobachter-Musters ist das Protokollieren von Abläufen. Für die Verfolgung, und damit für das Protokollieren, der Testausführung bietet PHPUnit die Schnittstelle Beispiel: Eine Implementierung der Schnittstelle
Im nächsten Beispiel wird zunächst ein neues Objekt der Klasse Beispiel: Ausführung eines Testfalls unter Beobachtung des SimpleTestListener
Ausführung des Testfalls "testDoSomething" wurde gestartet. Ausführung des Testfalls "testDoSomething" wurde beendet. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Beobachter. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.behavioral-patterns.observer.html [01.03.2008, 16:30 GMT+1]) 4.3 Das Decorator-MusterProblem Objekte sollen dynamisch um Funktionalität erweitert werden können, ohne die zugehörige Klasse durch Unterklassenbildung statisch erweitern zu müssen. Motivation Oft kommt es vor, dass man die Funktionalität eines Objektes dynamisch und transparent erweitern oder verändern möchte. Dem statischen Ansatz der Erweiterung der Klassenhierarchie um entsprechende Unterklassen ist meist eine objektbasierte Lösung vorzuziehen. Hierbei kann die Funktionalität verschiedener Objekte durch lose Kopplung zur Laufzeit "zusammengesteckt" werden. Lösung Die erweiterte oder veränderte Funktionalität wird in einem so genannten Dekorierer-Objekt modelliert, das dieselbe Schnittstelle wie das zu dekorierende Objekt anbietet. Dies erlaubt die transparente Benutzung des Dekorierer-Objekts durch Verwender des ursprünglichen Objektes. Das Dekorierer-Objekt leitet Methodenaufrufe zur Ausführung an das aggregierte, zu dekorierende Objekt weiter und kann seine zusätzliche Funktionalität vor oder nach diesem Methodenaufruf ausführen. Anwendungsbeispiele Die Klassen FilterIterator und LimitIterator sind zwei Dekorierer. Objekte dieser Iterator-Klassen dekorieren ein anderes Iterator-Objekt und filtern oder limitieren dessen Elemente. Für die Anpassung und Erweiterung der Testausführung bietet PHPUnit die Dekorierung der Klasse Beispiel: Die Klasse
Beispiel: Die Klasse
Für die dekorierte Ausführung eines Tests, in unserem Beispiel also die wiederholte Ausführung einer Testfallmethode, wird zunächst ein Objekt der Testfallklasse erzeugt. Dieses wird anschließend dem Konstruktor der Dekorierer-Klasse (hier Beispiel: Wiederholte Testausführung mit
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Dekorierer. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.structural-patterns.decorator.html [01.03.2008, 16:30 GMT+1]) 4.4 Das Factory-MusterDas Factory-Muster erlaubt die Instanziierung eines Objektes zur Laufzeit. Da es für die "Herstellung" von Objekten zuständig ist, wird es Factory-Muster gennant. Beispiel: Factory Method
Diese Methode definiert in einer Klasse erlaubt das Laden von Treibern "on the fly". Wenn z.B. die Beispiel Klasse eine Datenbank Abstraktionsklasse wäre, so könnten die Treiber MySQL und SQLite wie folgt geladen werden:
(Quelle: PHPBuilder (01.03.2008): Factory Method. Online im Internet: URL: http://phpbuilder.com/manual/en/language.oop5.patterns.php [01.03.2008, 16:30 GMT+1]) Eine Factory ist also ein Hilfsmittel zur Erzeugung von Objekten. Sie wird verwendet, wenn die zur Generierung des Objekts verwendete Klasse erst zur Laufzeit bekannt ist. Ein weiteres Beispiel für eine Factory Methode könnte folgendermaßen aussehen:
In diesem Beispiel ist MDB2::connect() die factory-Methode und liefert ein Datenbank-Verbindungs-Objekt oder im Fehlerfall ein PEAR-Error-Objekt zurück. (Quelle: php::bar (01.03.2008): Factory Method. Online im Internet: URL: http://www.phpbar.de/w/Factory_Method [01.03.2008, 16:30 GMT+1]) Abstract FactoryProblem Objekte verwandter Klassen sollen erzeugt werden, so dass die zu verwendende Klasse erst zur Laufzeit festgelegt werden kann. Motivation Eine Aufgabe kann auf unterschiedliche Arten, beispielweise unter Verwendung verschiedener Protokolle oder Verfahren, durchgeführt werden. Die einzelnen Implementierungen sollen zur Laufzeit des Programms austauschbar sein. Lösung Die gemeinsame Funktionalität der einzelnen Implementierungen wird in einer abstrakten Basisklasse gekapselt. Die direkte Erzeugung von Objekten der Kindklassen dieser Basisklasse wird durch die Deklaration der entsprechenden Konstruktoren als protected oder private unterbunden. Für die Erzeugung von Objekten bietet die abstrakte Basisklasse eine statische Methode an, die anhand des übergebenen Parameters ein Objekt des gewünschten Typs erzeugt und zur Verfügung stellt. Anwendungsbeispiele Betrachten wir als Beispiel einen Online-Shop, der seinen Partnern ein Interface zum Produktkatalog zur Verfügung stellt. Der eine Partner wünscht eine Schnittstelle auf XML-RPC-Basis, ein weiterer würde gerne SOAP nutzen können, während ein dritter Geschäftspartner ein naives Protokoll auf der Basis von HTTP GET und POST verlangt. Die Programmlogik ist bei den drei Varianten gleich, sie wird also zunächst in einer Basisklasse gekapselt. Von dieser Basisklasse leiten sich drei Klassen, je eine für XML-RPC, SOAP und HTTP GET / POST ab. Diese abgeleiteten Klassen implementieren den jeweiligen Kommunikationsmechanismus. Bislang haben wir nur mit dem Konzept der Vererbung Coderedundanzen vermieden. Allerdings ist das Anlegen von Instanzen der drei Klassen aus der Applikation heraus noch nicht transparent, die Nutzung komplizierter als nötig, wie das folgende Beispiel zeigt. Beispiel: Verwendung der drei Klassen ohne abstrakte Fabrik
Nachdem die Auswahl des zu erzeugenden Objektes in der Methode Beispiel: Verwendung der drei Klassen mit abstrakter Fabrik
Das nächste Beispiel zeigt die Implementierung einer abstrakten Fabrik in PHP, das darauf folgende Beispiel das Grundgerüst einer konkreten Kindklasse, für die die Fabrik Objekte erzeugen kann. Beispiel: Die abstrakte Klasse PartnerInterface
Beispiel: Die konkrete Klasse
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Abstrakte Fabrik. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.creational-patterns.abstract-factory.html [02.03.2008, 21:00 GMT+1]) 4.5 Das Singleton-MusterProblem Die Anzahl der Objekte einer Klasse soll beschränkt werden. Motivation Oft ist nur ein Objekt oder ein Pool mit einer festen Anzahl von Objekten einer Klasse sinnvoll, beispielsweise bei der Kapselung von externen Ressourcen. Lösung Die Erzeugung von Objekten durch den new-Operator wird durch Deklaration des Konstruktors als protected oder private unterbunden. Das Klonen des Objektes wird durch die Deklaration der Methode Für die Objekterzeugung wird eine statische Methode, meist Abbildung: Struktur des Singleton-Patterns Beispiel: Singleton
Neues Objekt wird erzeugt. $a und $b referenzieren dasselbe Objekt. Anwendungsbeispiele Klassen, die externe Ressourcen wie beispielsweise Datenbankverbindungen kapseln, sind in der Regel gute Kandidaten für die Verwendung des Singleton-Musters. Mit seiner Methode getInstance() bietet dieses einen globalen Zugriffspunkt auf das (einzige) Objekt einer Klasse. Ohne diese Möglichkeit müsste beispielsweise in jeder Methode einer Anwendung, in der auf die Datenbank zugegriffen wird, ein neues Objekt der entsprechenden Klasse erzeugt werden oder aber ein solches Objekt als Parameter übergeben werden. Beide Alternativen zur Anwendung des Singleton-Musters sind "unschön", Erstere unter dem Aspekt der Performanz sogar ein großer Fehler. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Singleton. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.creational-patterns.singleton.html [02.03.2008, 21:00 GMT+1]) Das Singleton-Muster wird in Situationen angewandt, in denen nur eine einzige Instanz einer Klasse benötigt wird. Das bekannteste Beispiel dafür ist eine Datenbankverbindung. Mit diesem Muster implementiert macht der Entwickler diese eine Instanz einfach erreichbar von vielen anderen Objekten. Example: Singleton Function
Dies erlaubt eine einzige Instanz der Klasse Example aufzurufen.
(Quelle: PHPBuilder (01.03.2008): Singleton. Online im Internet: URL: http://phpbuilder.com/manual/en/language.oop5.patterns.php [01.03.2008, 16:30 GMT+1]) Abstract Singleton Ab PHP 5.3 möglich, da get_called_class() erst dort verfügbar:
(Quelle: php::bar (01.03.2008): Abstract Singleton. Online im Internet: URL: http://www.phpbar.de/w/Abstract_Singleton [01.03.2008, 16:30 GMT+1]) 4.6 Das Template Method-MusterProblem Unterklassen soll es ermöglicht werden, bestimmte Schritte einer Operation zu überschreiben, ohne deren Struktur zu verändern. Motivation Lässt sich eine Aufgabe in Einzeloperationen zerlegen, von denen eine oder mehrere unterschiedlich implementiert werden können, so bietet sich das Zusammenfassen der gemeinsamen Operationen in einer Basisklasse an. Die Unterklassen dieser Klassen implementieren ihrerseits nur die Einzelschritte, in denen sie sich voneinander unterscheiden. Lösung In der Basisklasse wird das Grundgerüst der Operation in einer als public final deklarierten Methode implementiert. Muss ein Teilschritt, dessen Implementierung an eine Unterklasse delegiert werden soll, implementiert werden, so wird die entsprechende Methode in der Basisklasse als abstract protected deklariert. Soll die Implementierung hingegen optional sein, so wird die Methode lediglich als protected deklariert und verfügt in der Basisklasse nur über einen leeren Methodenrumpf. Anwendungsbeispiele Eine Schablonenmethode wird gerne eingesetzt, um eine Ausgabe in unterschiedlichen Formaten darstellen zu können. Die Klasse PHPUnit2_Extensions_CodeCoverage_Renderer des PHPUnit-Paketes kombiniert das Entwurfsmuster der abstrakten Fabrik (siehe „Abstrakte Fabrik“) mit einer Schablonenmethode und ermöglicht so die Darstellung von Code-Coverage-Informationen in unterschiedlichen Formaten durch entsprechende Unterklassen. Die Methode render() stellt hierbei die Schablone dar und ruft die den Einzelschritten entsprechenden Methoden in der vorgegebenen Reihenfolge auf. Beispiel: Die abstrakte Klasse
Beispiel: Die Klasse
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Schablone. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.behavioral-patterns.template-method.html [02.03.2008, 21:00 GMT+1]) 4.7 Das Iterator MusterProblem Der generische Zugriff auf die Elemente einer Sammlung soll ermöglicht werden, ohne dass die Struktur der Sammlung oder die Implementierung der einzelnen Elemente bekannt sein muss. Motivation Die Elemente unterschiedlicher Sammlungen, bei denen es sich beispielsweise um die Zeilen einer Textdatei, die Elemente eines XML-Dokumentes oder die Ergebniszeilen einer Datenbankabfrage handeln kann, sollen einheitlich verarbeitet werden können. Lösung wird in den folgenden Abschnitten beschrieben. (Quelle: Iterator-Muster, Sebastian Bergmann, 02.03.2008, 20:00 GMT+1) (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Iterator. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.behavioral-patterns.iterator.html [02.03.2008, 21:00 GMT+1]) EinleitungDas Aufzählen oder Durchlaufen der Elemente einer Menge ist ein wiederkehrendes Problem. Beispiele für solche Mengen sind Arrays, die Zeilen einer Textdatei, die Elemente eines XML-Dokumentes oder die Ergebniszeilen einer Datenbankabfrage. In PHP 3 erfolgte das Durchlaufen eines numerisch indizierten Arrays mit Hilfe einer Beispiel: Iterieren von Arrays in PHP 3
Assoziative Arrays mussten in PHP 3 mit Beispiel: Iterieren von assoziativen Arrays in PHP 3
In PHP 4 wurde mit foreach ein neuer Operator eingeführt, der die Arbeit mit assoziativen und numerisch indizierten Arrays vereinfachte und vereinheitlichte. Nun war es nicht mehr Aufgabe des Programmierers, sich um Dinge wie Indexgrenzen oder das Fortschreiten zum nächsten Element zu kümmern. Beispiel: Iterieren von Arrays in PHP 4 1 2 3 4 5 6 7 8 9 10 key: value Bereits in PHP 4 war es möglich, den Beispiel: Verwendung von
a: 1 b: 2 An dieser Stelle wünschen wir uns ein Konzept, das die Vereinheitlichung von assoziativen und numerisch indizierten Arrays verallgemeinert und auf Objekte erweitert. Dieses Konzept ist das Iterator Entwurfsmuster, das wir im Folgenden diskutieren wollen. Ein Iterator ermöglicht den generischen Zugriff auf die Elemente einer Menge, ohne dass die Struktur der Menge oder die Implementierung der einzelnen Elemente bekannt sein muss. Bei den Elementen einer solchen Menge kann es sich beispielsweise um die Zeilen einer Textdatei, die Elemente eines XML-Dokumentes oder die Ergebniszeilen einer Datenbankabfrage handeln. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Iteratoren. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.iterators.html [02.03.2008, 21:00 GMT+1]) Die Iterator-Schnittstellen von PHP 5PHP bietet mit den Schnittstellen Abbildung: Die Schnittstellen Iterator, IteratorAggregate und Traversable Wendet man den
Das folgende Beispiel zeigt eine Implementierung der Schnittstelle Iterator, die über die (durch Leerzeichen getrennten) Teile eines Strings iteriert. Beispiel: Eine Implementierung der Schnittstelle Iterator
Beispiel: Implizite Verwendung von Iteratoren mit dem foreach-Operator
Das Iterieren eines Objektes, dessen Klasse die Schnittstelle Iterator implementiert, kann neben der impliziten Verwendung durch die Benutzung des foreach-Operators (s. letztes Beispiel) auch explizit durch die Verwendung der in der Schnittstelle vereinbarten Methoden erfolgen (nächstes Beispiel). Hierbei wird deutlich, dass die Methoden der Schnittstelle Iterator den aus PHP 3 bekannten Funktionen für das Durchlaufen von assoziativen Arrays entsprechen. Beispiel: Explizite Verwendung von Iteratoren
Bislang müssen wir für eine Klasse, für die eine entsprechende Iterator-Klasse zur Verfügung steht, "von Hand" ein Iterator-Objekt erzeugen. Hier hilft die Implementierung der Schnittstelle IteratorAggregate durch die Klasse. Deren Methode getIterator liefert ein Objekt der entsprechenden Iterator-Klasse. Verwendet man ein Objekt, das die Schnittstelle IteratorAggregate anbietet, mit dem foreach-Operator, so ruft der PHP-Interpreter automatisch getIterator auf und verwendet das zurückgegebene Iterator-Objekt. Das nächste Beispiel zeigt eine Klasse String, die die Schnittstelle Beispiel: Eine Implementierung der Schnittstelle
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Die Iterator-Schnittstelle von PHP 5. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.iterators.interfaces.html [02.03.2008, 21:00 GMT+1]) Die Standard PHP Library (SPL)Die Standard PHP Library (SPL) ist fester Bestandteil von PHP 5 und baut auf den Iterator-Schnittstellen auf. Sie tritt an, ein PHP-Pendant zu der von C++ bekannten Standard Template Library (STL) zu werden. In PHP 5.0 umfasst die Standard PHP Library die folgenden Klassen und Schnittstellen:
Betrachten wir einmal die Aufgabe, die Elemente einer Menge gefiltert zu verarbeiten. Mit Hilfe eines Iterators sind wir bereits in der Lage, die Elemente einer beliebigen Menge zu durchlaufen. Eine einfache Möglichkeit, aus einer Menge von Teilstrings nur diejenigen auszugeben, die mit "Bar" beginnen, sehen wir im folgenden Beispiel. Beispiel: Filtern einer Menge von Teilstrings Bar Barbara Im letzten Beispiel wenden wir die Filterregel direkt in der Schleife an, mit der wir die Elemente der Menge durchlaufen. Dies wird jedoch zum einen bei komplexeren Filteroperationen schnell unübersichtlich, zum anderen stößt diese Methode an Grenzen, wenn es darum geht, Filterregeln dynamisch auszutauschen oder zu kombinieren. Die Standard PHP Library bietet für dieses Problem die Möglichkeit an, Iterator-Objekte zu kombinieren. Hierbei kontrolliert ein äußerer Iterator einen inneren Iterator. Der innere Iterator arbeitet auf der eigentlichen Menge von Elementen, die verarbeitet werden soll. Der äußere Iterator entscheidet jedoch für jedes dieser Elemente, ob und in welcher Weise es an den Verwender zurückgegeben werden soll. Hinter diesem Konzept steht mit dem Dekorierer ein weiteres Entwurfsmuster, das wir später noch genauer behandeln werden. Abbildung: FilterIterator, LimitIterator und SeekableIterator Die abstrakte Klasse FilterIterator erlaubt das Schreiben einer Klasse für die Verwendung als äußeren Iterator. Dieser filtert die Elemente des inneren Iterators, der als Objekt dem Konstruktor zu übergeben ist. Die Filterkriterien sind hierbei in der Methode Das nächste Beispiel zeigt eine von Beispiel: Eine
Bar Barbara Ein Objekt der Klasse LimitIterator erlaubt das Limitieren der Elemente des inneren Iterators, für den es als äußerer Iterator in Aktion tritt. Der Konstruktor erwartet neben dem inneren Iterator zwei optionale Parameter Beispiel: Verwendung der Klasse
Der äußere Iterator eines inneren Iterators kann seinerseits als innerer Iterator für einen weiteren Iterator dienen. Im nächsten Beispiel ist der StringFilterIterator einerseits äußerer Iterator für den StringIterator, andererseits aber auch innerer Iterator für den LimitIterator. Beispiel: Kombination von
Im Standardfall nutzt ein Für Mengen, bei denen die Position auf ein bestimmtes Element direkt gesetzt werden kann, bietet sich die Erweiterung der Klasse LimitIterator durch eine Kindklasse an, die zusätzlich die Schnittstelle (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Die Standard PHP Library (SPL). Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.iterators.spl.html [02.03.2008, 21:00 GMT+1]) Die Schnittstelle ArrayAccessObjekte von Klassen, die die Schnittstelle Abbildung: Die Schnittstelle ArrayAccess Die vier zu implementierenden Methoden sind:
Das nächste Beispiel zeigt eine Implementierung der normalen PHP-Array-Datenstruktur als Klasse unter Verwendung der Schnittstelle ArrayAccess. Beispiel: Eine Implementierung der ArrayAccess-Schnittstelle
Nachdem wir uns mit der grundsätzlichen Verwendung der Schnittstelle Im folgenden Beispiel benutzen wir eine Implementierung der Schnittstelle Beispiel: Die Klasse
(Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Die Schnittstelle ArrayAccess. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/oop.iterators.arrayaccess.html [01.03.2008, 16:30 GMT+1]) 4.8 Das Proxy-MusterProblem Der Zugriff auf ein Objekt soll durch ein vorgelagertes Stellvertreterobjekt kontrolliert werden. Motivation Funktionalität wie beispielsweise Zugriffskontrolle, die Verzögerung von Berechnungen, oder das Vorhalten von bereits berechneten Ergebnissen möchte man in einer eigenen Klasse modellieren. Ein Objekt dieser Klasse wird als Stellvertreter für ein anderes Objekt verwendet. Lösung Die beiden Klassen, von denen die Objekte der einen Objekte der anderen vertreten sollen, implementieren dieselbe Schnittstelle. Über den Konstruktor erhält das Stellvertreterobjekt eine Referenz auf das zu vertretende Objekt, um Methodenaufrufe gegebenfalls an dieses delegieren zu können. Anwendungsbeispiele Durch die Vorlagerung eines Stellvertreterobjektes kann bei einem Methodenaufruf entschieden werden, ob der unter Umständen teure Aufruf der Methode des eigentlichen Objektes überhaupt ausgeführt werden muss. Wird die Methode zum wiederholten Male mit demselben Parameter aufgerufen, und liefert die Methode für identische Parameter immer dasselbe Ergebnis, so kann die wiederholte Ausführung durch Speicherung des Ergebnisses vermieden werden. Die beiden folgenden Beispiele zeigen eine Schnittstelle Beispiel: Die Schnittstelle TeureBerechnung
Beispiel: Die Klasse Beispiel
Die Klasse Beispiel: Die Klasse BeispielStellvertreter
Das Stellvertreter-Muster spielt in der verteilten Programmierung ebenfalls eine Rolle. So werden beispielsweise die über einen Webdienst angebotenen Methoden auf der Client-Seite von einem Stellvertreter-Objekt repräsentiert. In ihrer Implementierung ähneln sich die Entwurfsmuster Dekorierer und Stellvertreter. Der Unterschied zwischen diesen beiden Mustern liegt im Ziel, das sie verfolgen. Ein Dekorierer wird genutzt, um die Funktionalität eines bestehenden Objektes zu erweitern oder zu verändern, während ein Stellvertreter den Zugriff auf das bestehende Objekt kontrolliert. (Quelle: Bergman, Sebastian (2005): Professionelle Softwareentwicklung mit PHP5. Stellvertreter. Online im Internet: URL: http://professionelle-softwareentwicklung-mit-php5.de/design-patterns.structural-patterns.proxy.html [01.03.2008, 16:30 GMT+1]) 5. Anti-Pattern5.1 Außerirdische SpinnenEin Design, das den Namen nicht verdient. Sehr gesprächige, kommunikative Objekte, die sich alle gegenseitig kennen. Überhaupt keine Nutzung von Entwurfsmustern. Bei n Objekten gibt es n*(n-1)/2 Kommunikationspaare (englisch Alien spiders). 5.2 GasfabrikEine Gasfabrik ist ein unnötig komplexes Systemdesign für eine recht einfache Aufgabenstellung. Beispielhafte Nutzung: „Ich wollte eine Softwarelösung haben, keine Gasfabrik.“ (englisch Gas factory) 5.3 GottobjektEin Objekt, das zu viel weiß respektive zu viel macht. Aufteilung nach Verantwortlichkeiten, Kapselung und Einhaltung von Entwurfsmustern helfen diesem Muster zu begegnen. Auch als Gottklasse (God class) und Blob bekannt (englisch God object). 5.4 Innere Plattform-EffektEin System besitzt derartig weitreichende Konfigurationsmöglichkeiten, dass es letztlich zu einer schwachen Kopie der Plattform wird, mittels der es gebaut wurde. Ein Beispiel sind "flexible" Datenmodelle, die auf konkrete (anwendungsbezogene) Datenbanktabellen verzichten und statt dessen mittels allgemeiner Tabellen eine eigene Verwaltungsschicht für die Datenstruktur implementieren. Derartige Systeme sind typischerweise schwer zu beherrschen und leiden unter erheblichen Performanceproblemen. (englisch Inner platform effect) 5.5 Spaghetti-CodeEine sehr kompakte Systemstruktur, deren Kontrollfluss einem Topf Spaghetti ähnelt. 5.6 Sumo-HochzeitEin Fat Client ist unnatürlich stark abhängig von der Datenbank. In der Datenbank ist sehr viel Logik in Form der datenbankeigenen Programmiersprache positioniert, in Oracle zum Beispiel mit PL/SQL. Die ganze Architektur ist sehr unflexibel. Soll die Anwendung zu einer Internet-Anwendung migriert oder die Datenbank gewechselt werden, so müssen auf beiden Schichten (Client und Datenhaltung) viele Bereiche neu entwickelt werden. Die Systeme sind nicht entkoppelt (englisch Sumo Marriage). 5.7 ZwiebelNeue Funktionalität wird um (oder über) die alte gelegt. Häufig zu beobachten, wenn ein Entwickler ein Programm erweitern soll, welches er nicht geschrieben hat. Der Entwickler möchte oder kann die bereits existente Lösung nicht komplett verstehen, und setzt seine neue Lösung einfach drüber. Dies führt mit einer Vielzahl von Versionen und unterschiedlichen Entwicklern über die Jahre zu einem Zwiebel-System (englisch Onion). 5.8 Programmierung mittels Copy & PasteDer Programmierer entwickelt den Code nicht neu, sondern bedient sich bereits existenter Quelltexte, aus denen er Passagen herauskopiert. Die Gefahr ist sehr groß, dass er Fehler mitkopiert oder die Kopie für den neuen Bereich nicht optimal einsatzbereit ist. Der Entwickler reflektiert weniger über sein Programm, als wenn er jede Zeile selbst entwickeln würde. Fehleranfälliges Vorgehen, wenn der Entwickler nicht weiß, was er eigentlich macht. Weit verbreitet (englisch Copy And Paste Programming). Dieses Vorgehen ist auch unter dem Begriff „Verteile und beherrsche nicht“ (als zynische Anspielung auf das informatische Konzept des "divide and conquer" / divide et impera / Teile und herrsche) bekannt. 5.9 LavaflussBeschreibt den Umstand, dass in einer Anwendung immer mehr „toter Quelltext“ herumliegt. Dieser wird nicht mehr genutzt. Statt ihn zu löschen, werden im Programm immer mehr Verzweigungen eingebaut, die um den besagten Quelltext herumlaufen oder auf ihn aufbauen (englisch Lava flow). 5.10 Switch-StatementVerhalten von Objekten gemäß ihrem Status (state) sollten mit Statusobjekten und dem state-Pattern gesteuert werden, nicht mit konditionalem Code. 5.11 Magic ValuesHierbei handelt es sich um Daten (Literale) mit besonderer Bedeutung. Sie sind hartkodiert (hardcoded) und nur mit besonderem Wissen über die konkrete Verwendung zu verstehen. Werte sollten zentral als Variable definiert werden, optimalerweise als typsicheres Objekt (typesafe). 5.12 Reservierte WörterDie Verwendung von reservierten Wörtern in SQL-Anweisungen kann zu schwer zu findenden Fehlern führen. Ein Austausch der Datenbank eines Herstellers gegen ein anderes Produkt kann dazu führen, dass weitere Namen als reserviert betrachtet werden müssen. (Quelle: Wikipedia (01.03.2008): Anti-Pattern. Online im Internet: URL: http://de.wikipedia.org/wiki/Anti-Pattern [01.03.2008, 16:30 GMT+1])
| |||
|