StudipCsDpAp

OO-Design & Entwurfsmuster

Coding Style Index?

Auf dieser Seite... (ausblenden)

  1.   1.  Motivation
    1.   1.1  Warum OOP?
  2.   2.  OOP in PHP
    1.   2.1  Polymorphie
    2.   2.2  Serialisierung von Objekten
    3.   2.3  Die Reflection API
    4.   2.4  Migration von PHP 4 zu PHP 5
    5.   2.5  Interzeptionsmethoden
  3.   3.  Entwurfsprinzipien
  4.   4.  Design-Pattern
    1.   4.1  Das Strategy-Muster
    2.   4.2  Das Observer-Muster
    3.   4.3  Das Decorator-Muster
    4.   4.4  Das Factory-Muster
    5.   4.5  Das Singleton-Muster
    6.   4.6  Das Template Method-Muster
    7.   4.7  Das Iterator Muster
    8.   4.8  Das Proxy-Muster
  5.   5.  Anti-Pattern
    1.   5.1  Außerirdische Spinnen
    2.   5.2  Gasfabrik
    3.   5.3  Gottobjekt
    4.   5.4  Innere Plattform-Effekt
    5.   5.5  Spaghetti-Code
    6.   5.6  Sumo-Hochzeit
    7.   5.7  Zwiebel
    8.   5.8  Programmierung mittels Copy & Paste
    9.   5.9  Lavafluss
    10.   5.10  Switch-Statement
    11.   5.11  Magic Values
    12.   5.12  Reservierte Wörter

1.  Motivation

1.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

  1. <?php
  2. $connection = mysql_connect('localhost', 'root', '');
  3.  
  4. if ($connection === FALSE) {
  5.     handle_error();
  6. }
  7.  
  8. if (mysql_select_db('test', $connection) === FALSE) {
  9.     handle_error(mysql_error($connection));
  10.  }
  11.  
  12. $result = mysql_query(
  13.     'SELECT spalte FROM tabelle',
  14.     $connection
  15. );
  16.  
  17. if ($result === FALSE) {
  18.     handle_error(mysql_error($connection));
  19. }
  20.  
  21. while(($row = mysql_fetch_assoc($result)) !== FALSE) {
  22.   // ...
  23. }
  24.  
  25.  
  26. function handle_error($message = '')
  27. {
  28.   // ...
  29. }
  30. ?>

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

  1. <?php
  2. class Db_MySql
  3. {
  4.     private $connection = NULL;
  5.     private $result = NULL;
  6.  
  7.     public function connect($host, $database, $user, $pass)
  8.     {
  9.         $this->connection = mysql_connect(
  10.             $host,
  11.             $user,
  12.             $pass,
  13.             TRUE
  14.         )
  15.         mysql_select_db($database, $this->connection);
  16.     }
  17.  
  18.     public function disconnect()
  19.     {
  20.         if (is_resource($this->connection)) {
  21.             mysql_close($this->connection);
  22.         }
  23.     }
  24.  
  25.     public function query($query) {
  26.         if (is_resource($this->connection)) {
  27.             if (is_resource($this->result)) {
  28.                 mysql_free_result($this->result);
  29.             }
  30.  
  31.             $this->result = mysql_query(
  32.                 $query,
  33.                 $this->connection
  34.             );
  35.         }
  36.     }
  37.  
  38.     public function fetchRow()
  39.     {
  40.         if (is_resource($this->result)) {
  41.             $row = mysql_fetch_assoc($this->result);
  42.  
  43.             if (is_array($row)) {
  44.                 return $row;
  45.             } else {
  46.                 return FALSE;
  47.             }
  48.         }
  49.     }
  50. }
  51. ?>

Verwendung der MySQL-Klasse

  1. <?php
  2. require_once 'DB_MySQL.php';
  3.  
  4. $mysql = new DB_MySQL;
  5. $mysql->connect('localhost', 'test', 'root', '');
  6. $mysql->query('SELECT spalte FROM tabelle');
  7.  
  8. while ($row = $mysql->fetchRow()) {
  9.   // ...
  10. }
  11.  
  12. $mysql->disconnect();
  13. ?>

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

  1. <?php
  2. class Vector
  3. {
  4.     public function add(Vector $vector)
  5.     {
  6.         // ...
  7.     }
  8. }
  9. ?>

Typprüfung mit dem instanceof-Operator

  1. <?php
  2. class Vector
  3. {
  4.     public function add($vector)
  5.     {
  6.         if (!($vector instanceof Vector)) {
  7.             die('Parameter muss vom Typ Vector sein.');
  8.         }
  9.  
  10.         // ...
  11.     }
  12. }
  13. ?>

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 PHP

2.1  Polymorphie

Mit 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

  1. class Klasse {
  2.   public void methode(String variable) {
  3.     System.out.println("Ein String wurde übergeben.");
  4.   }
  5.  
  6.   public void methode(int variable) {
  7.     System.out.println("Ein Integerwert wurde übergeben.");
  8.   }
  9. }

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

  1. <?php
  2. class Klasse {
  3.   public function methode($variable) {
  4.     if (is_string($variable)) {
  5.       print "Ein String wurde übergeben.\n";
  6.     }
  7.  
  8.     else if (is_integer($variable)) {
  9.       print "Ein Integerwert wurde übergeben.\n";
  10.     }
  11.   }
  12. }
  13. ?>

(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 Objekten

Objekte 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()

  1. <?php
  2. class Klasse {
  3.   function __sleep() {
  4.     print "__sleep() aufgerufen.\n";
  5.     return get_class_vars(get_class($this));
  6.   }
  7.  
  8.   function __wakeup() {
  9.     print "__wakeup() aufgerufen.\n";
  10.   }
  11. }
  12.  
  13. $objekt               = new Klasse;
  14. $serialisiertesObjekt = serialize($objekt);
  15. $objekt               = unserialize($serialisiertesObjekt);
  16. ?>

__sleep() aufgerufen. __wakeup() aufgerufen.

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 API

Die 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.

  • Für den "Blick in das Innere" von Klassen und Objekten bietet PHP die so genannte Reflection API an, die die folgenden Klassen und Schnittstellen umfasst:
  • Reflector ist die Schnittstelle, die von allen Klassen der Reflection API implementiert wird.
  • Reflection ist eine statische Hilfsklasse, die die Arbeit mit Reflector-Objekten erleichtert.
  • ReflectionException ist die von Exception abgeleitete Ausnahme-Klasse, die für das Signalisieren von Fehlern innerhalb der Reflection API verwendet wird.
  • ReflectionExtension repräsentiert die Informationen über eine PHP-Erweiterung.
  • ReflectionFunction repräsentiert die Informationen über eine Funktion.
  • ReflectionParameter repräsentiert die Informationen über einen Parameter einer Funktion oder Methode.
  • ReflectionClass repräsentiert die Informationen über eine Klasse.
  • ReflectionObject ist eine Erweiterung von ReflectionClass und repräsentiert die Informationen über ein Objekt.
  • ReflectionMethod ist eine Erweiterung von ReflectionFunction und repräsentiert die Informationen über eine Methode.
  • ReflectionProperty repräsentiert die Informationen über eine Instanzvariable einer Klasse oder eines Objektes.

Allen Klassen der Reflection API gemein ist die statische Methode export(). Diese liefert eine textuelle Darstellung des reflektierten Sprachobjekts.

Beispiel: Verwendung von ReflectionClass::export()

  1. <?php
  2. class Klasse {
  3.   public $public;
  4.   protected $protected;
  5.   private $privateStatic;
  6.  
  7.   public function methode(Klasse $objekt) {
  8.     $objekt = new ReflectionObject($objekt);
  9.     print $objekt->getName();
  10.   }
  11. }
  12. print ReflectionClass::export('Klasse');
  13. ?>
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 ReflectionClass. Diese bietet die folgenden Methoden:

  • getFileName(), getStartLine(), getEndLine(), getDocComment(), getExtension() und getExtensionName() liefern Informationen über die Deklaration der Klasse in einer Quelltextdatei beziehungsweise in einer PHP-Erweiterung.
  • isInternal(), isUserDefined(), isInstantiable(), isInterface(), isAbstract(), isFinal(), getParentClass(), isSubclassOf(), implementsInterface() und isIterateable() liefern Informationen über Eigenschaften der Klasse wie beispielsweise Vererbungsbeziehungen.
  • getMethod(), getMethods(), getProperty(), getProperties(), getConstants() und getConstant() liefern beispielsweise Objekte der Klassen ReflectionMethod und ReflectionProperty, um mit den Methoden, Instanzvariablen und Konstanten einer Klasse zu arbeiten.

Im nächsten Beispiel erzeugen wir zunächst ein Objekt der Klasse ReflectionClass für die Klasse Klasse aus vorherigem Beispiel. Über die Methode getMethod() dieses Objektes gelangen wir an ein Objekt der Klasse ReflectionMethod, das die Methode Klasse::Methode repräsentiert. Diese Methode können wir mit ReflectionMethod::invoke() ausführen. Als ersten Parameter müssen wir das Objekt der zugehörigen Klasse (in unserem Beispiel Klasse) übergeben, auf dem die Methode ausgeführt werden soll. Danach folgen die Parameter der Methode, die aufgerufen werden soll.

Beispiel: ReflectionClass und ReflectionMethod im Einsatz

  1. <?php
  2. require_once 'Klasse.php';
  3.  
  4. $klasse = new ReflectionClass('Klasse');
  5. $objekt = new Klasse;
  6.  
  7. $methode = $klasse->getMethod('methode');
  8. $methode->invoke($objekt, $objekt);
  9. ?>

Klasse

(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 5

Bei 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:

  • abstract
  • catch
  • clone
  • final
  • implements
  • interface
  • private
  • protected
  • public
  • throw
  • try

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:

  • __autoload
  • __call
  • __clone
  • __construct
  • __destruct
  • __get
  • __set
  • __toString

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  Interzeptionsmethoden

Neben Konstruktor und Destruktor sowie den Methoden __sleep() und __wakeup() bietet PHP noch eine Reihe weiterer spezieller Methoden an, die für bestimmte Ereignisse automatisch aufgerufen werden. Da diese Methoden die entsprechenden Ereignisse in gewisser Weise "abfangen", nennt man sie Interzeptormethoden.

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.

  • __autoload($className): Wird aufgerufen, wenn ein Objekt der Klasse $className erzeugt werden soll, die Klasse aber nicht deklariert ist.
  • __get($memberName): Wird aufgerufen, wenn lesend auf die Instanzvariable $memberName eines Objektes zugegriffen wird, die Instanzvariable aber nicht gesetzt ist.
  • __set($memberName, $value): Wird aufgerufen, wenn schreibend auf die Instanzvariable $memberName eines Objektes zugegriffen wird und sie vorher nicht gesetzt war. Der zweite Parameter $value enthält den Wert, den die Instanzvariable erhalten soll.
  • __call($methodName, $parameters): Wird aufgerufen, wenn eine nicht deklarierte Methode $methodName auf einem Objekt aufgerufen wird. Der zweite Parameter $parameters enthält die Parameter des Methodenaufrufes.
  • __toString(): Wird aufgerufen, wenn eine Typumwandlung eines Objektes in einen String durchgeführt werden soll.

(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.  Entwurfsprinzipien

Die OO-Basics (Abstraktion, Kapselung, Polymorphismus, Vererbung) machen nicht allein ein gutes OO-Design. Gute Entwürfe sind wiederverwendbar, erweiterbar und wartbar.

  1. Kapselung von veränderbaren Teilen: Identifizieren Sie die Aspekte Ihrer Anwendung, die sich ändern können, und trennen Sie sie von denen, die konstant bleiben.
  2. Supertyp durch Interface oder abstrakte Superklasse: Programmieren Sie auf eine Schnittstelle, nicht auf eine Implementierung.
  3. Komposition: Ziehen Sie Komposition (HAT-EIN) der Vererbung (IST-EIN) vor.

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:

  • Entwurfsmuster sind kein Allheilmittel!
Originalität ist nach wie vor bei der Anwendung der Entwurfsmuster gefragt.
  • Entwurfsmuster sind keine Algorithmen!
Algorithmen lösen feinkörnigere Probleme (Suchen, Sortieren) und bieten weniger Freiheitsgrade in der Implementierung.
  • Entwurfsmuster sind keine Frameworks!
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:

  • Erzeugungsmuster
  • Strukturmuster
  • Verhaltensmuster

4.  Design-Pattern

(siehe auch: Entwurfsmuster)

4.1  Das Strategy-Muster

Das 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:

  • Klasse Ente nimmt als Komposition die Supertypen der Verhaltensweisen Flugverhalten und Quakverhalten auf. Klasse Ente hat die Unterklassen StockEnte, MoorEnte, GummiEnte, LockEnte.
  • Flugverhalten und Quakverhalten ist jeweils gekapselt durch Interfaces (Supertyp) und deren Implementierungen. Diese Implementierungen sind leicht austauschbar.
  • Klasse Ente delegiert nun die Verhaltensweisen und ruft in den eigenen Methoden die jeweils entsprechende Methode der Verhaltensweisen (des Supertyps) auf statt diese selbst zu implementieren. Der dynamisch Austausch von Verhaltensweisen zur Laufzeit wird durch Getter- und Setter-Methoden ermöglicht.

Attach:ClassDiagramStrategyPattern.png Δ


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

  1. <?php
  2. interface CompareStrategy {
  3.     public function compare($a, $b);
  4. }
  5. ?>

Beispiel: Die Klasse AscendingCompare

  1. <?php
  2. require_once 'CompareStrategy.php';
  3.  
  4. class AscendingCompare implements CompareStrategy {
  5.   public function compare($a, $b) {
  6.     return ($a == $b) ? 0 : ($a > $b) ? 1 : -1;
  7.   }
  8. }
  9. ?>

Beispiel: Die Klasse DescendingCompare

  1. <?php
  2. require_once 'CompareStrategy.php';
  3.  
  4. class DescendingCompare implements CompareStrategy {
  5.   public function compare($a, $b) {
  6.     return ($a == $b) ? 0 : ($a < $b) ? 1 : -1;
  7.   }
  8. }
  9. ?>

Beispiel: Die Klasse BubbleSort

  1. <?php
  2. require_once 'CompareStrategy.php';
  3. require_once 'AscendingCompare.php';
  4. require_once 'DescendingCompare.php';
  5.  
  6. class BubbleSort {
  7.   private $strategy;
  8.  
  9.   public function setStrategy(CompareStrategy $strategy) {
  10.     $this->strategy = $strategy;
  11.   }
  12.  
  13.   public function sort($array) {
  14.     for ($i = sizeof($array)-1; $i >= 0; --$i) {
  15.       for ($j = 0; $j < $i; ++$j ) {
  16.         $cmp = $this->strategy->compare(
  17.           $array[$j],
  18.           $array[$j+1]
  19.         );
  20.  
  21.         if ($cmp > 0) {
  22.           $tmp         = $array[$j];
  23.           $array[$j]   = $array[$j+1];
  24.           $array[$j+1] = $tmp;
  25.         }
  26.       }
  27.     }
  28.  
  29.     return $array;
  30.   }
  31. }
  32.  
  33. $bs = new BubbleSort;
  34.  
  35. $bs->setStrategy(new AscendingCompare);
  36. print_r($bs->sort(array(22, 4, 1978)));
  37.  
  38. $bs->setStrategy(new DescendingCompare);
  39. print_r($bs->sort(array(22, 4, 1978)));
  40. ?>
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-Muster

Problem

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:

attach($observer)

Registriert das Objekt $observer zur Benachrichtigung bei Änderung des Zustands des Objektes, dessen attach()-Methode aufgerufen wird.

detach($observer)

Hebt die Registrierung des Objektes $observer zur Benachrichtigung bei Änderung des Zustands des Objektes, dessen detach()-Methode aufgerufen wird.

getState()

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 notify() des Subject-Objektes. Diese Methode ruft auf jedem als Beobachter registrierten Objekt dessen update()-Methode auf, die von den Beobachter-Objekten bereitzustellen ist.

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

  1. <?php
  2. require_once 'Observer.php';
  3.  
  4. abstract class Subject {
  5.   protected $observers = array();
  6.  
  7.   public function attach(Observer $observer) {
  8.       $this->observers[] = $observer;
  9.   }
  10.  
  11.   public function detach(Observer $observer) {
  12.     for ($i = 0; $i < sizeof($this->observers); $i++) {
  13.       if ($this->observers[$i] === $observer) {
  14.         unset($this->observers[$i]);
  15.       }
  16.     }
  17.   }
  18.  
  19.   protected function notify() {
  20.     for ($i = 0; $i < sizeof($this->observers); $i++) {
  21.       $this->observers[$i]->update();
  22.     }
  23.   }
  24.  
  25.   public abstract function getState();
  26. }
  27. ?>

Beispiel: Die abstrakte Klasse Observer

  1. <?php
  2. require_once 'Subject.php';
  3.  
  4. abstract class Observer {
  5.   protected $subject = NULL;
  6.  
  7.   public function attach(Subject $subject) {
  8.     $this->subject = $subject;
  9.     $this->subject->attach($this);
  10.   }
  11.  
  12.   public function detach() {
  13.     if ($this->subject !== NULL) {
  14.       $this->subject->detach($this);
  15.     }
  16.   }
  17.  
  18.   public abstract function update();
  19. }
  20. ?>

Beispiel: Die Klasse ConcreteSubject

  1. <?php
  2. require_once 'Subject.php';
  3.  
  4. class ConcreteSubject extends Subject {
  5.   protected $state = NULL;
  6.  
  7.   public function getState() {
  8.     return $this->state;
  9.   }
  10.  
  11.   public function doSomething() {
  12.     // ...
  13.  
  14.     $this->notify();
  15.   }
  16. }
  17. ?>

Beispiel: Die Klasse ConcreteObserver

  1. <?php
  2. require_once 'Observer.php';
  3.  
  4. class ConcreteObserver extends Observer {
  5.   protected $state = NULL;
  6.  
  7.   public function __construct(Subject $subject) {
  8.     $this->attach($subject);
  9.   }
  10.  
  11.   public function update() {
  12.     print 'ConcreteObserver::update()';
  13.  
  14.     $this->state = $this->subject->getState();
  15.   }
  16. }
  17. ?>

Beispiel: Verwendung von Subject und Observer

  1. <?php
  2. require_once 'ConcreteSubject.php';
  3. require_once 'ConcreteObserver.php';
  4.  
  5. $subject  = new ConcreteSubject;
  6. $observer = new ConcreteObserver($subject);
  7.  
  8. $subject->doSomething();
  9. $observer->detach();
  10. $subject->doSomething();
  11. ?>

ConcreteObserver::update()

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 PHPUnit2_Framework_TestListener an. Objekte von Klassen, die diese implementieren, können über eine entsprechende Methode (addListener($listener)) an ein Objekt der Klasse PHPUnit2_Framework_TestResult "angehängt" werden, um dieses zu beobachten und so die Testausführung zu protokollieren.

Beispiel: Eine Implementierung der Schnittstelle PHPUnit2_Framework_TestListener

  1. <?php
  2. require_once 'PHPUnit2/Framework/TestListener.php';
  3.  
  4. class SimpleTestListener
  5. implements PHPUnit2_Framework_TestListener {
  6.   public function
  7.   addError(PHPUnit2_Framework_Test $test, Exception $e) {
  8.     printf(
  9.       'Bei der Ausführung des Testfalls "%s"' .
  10.       " trat ein Fehler auf.\n",
  11.       $test->getName()
  12.     );
  13.   }
  14.  
  15.   public function
  16.   addFailure(PHPUnit2_Framework_Test $test,
  17.              PHPUnit2_Framework_AssertionFailedError $e) {
  18.     printf(
  19.       "Der Testfall \"%s\" schlug fehl.\n",
  20.       $test->getName()
  21.     );
  22.   }
  23.  
  24.   public function
  25.   addIncompleteTest(PHPUnit2_Framework_Test $test,
  26.                     Exception $e) {
  27.     printf(
  28.       "Der Testfall \"%s\" wurde nicht implementiert.\n",
  29.       $test->getName()
  30.     );
  31.   }
  32.  
  33.   public function startTest(PHPUnit2_Framework_Test $test) {
  34.     printf(
  35.       "Ausführung des Testfalls \"%s\" wurde gestartet.\n",
  36.       $test->getName()
  37.     );
  38.   }
  39.  
  40.   public function endTest(PHPUnit2_Framework_Test $test) {
  41.     printf(
  42.       "Ausführung des Testfalls \"%s\" wurde beendet.\n",
  43.       $test->getName()
  44.     );
  45.   }
  46.  
  47.   public function
  48.   startTestSuite(PHPUnit2_Framework_TestSuite $suite) {
  49.   }
  50.  
  51.   public function
  52.   endTestSuite(PHPUnit2_Framework_TestSuite $suite) {
  53.   }
  54. }
  55. ?>

Im nächsten Beispiel wird zunächst ein neues Objekt der Klasse PHPUnit2_Framework_TestResult erzeugt. Diesem wird im Anschluss ein Objekt der Klasse SimpleTestListener aus dem letzten Beispiel als Listener hinzugefügt. Schließlich wird das PHPUnit2_Framework_TestResult-Objekt genutzt, um den testDoSomething Testfall auszuführen.

Beispiel: Ausführung eines Testfalls unter Beobachtung des SimpleTestListener

  1. <?php
  2. require_once 'PHPUnit2/Framework/TestResult.php';
  3.  
  4. require_once 'SampleTest.php';
  5. require_once 'SimpleTestListener.php';
  6.  
  7. $test = new SampleTest('testDoSomething');
  8.  
  9. $result = new PHPUnit2_Framework_TestResult;
  10. $result->addListener(new SimpleTestListener);
  11. $result->run($test);
  12. ?>
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-Muster

Problem

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 PHPUnit2_Framework_TestCase an. Das Grundgerüst des entsprechenden Dekorierers liegt in Form der Klasse PHPUnit2_Extensions_TestDecorator vor. Diese implementiert die vom PHPUnit-Framework für die Testausführung benötigte Schnittstelle PHPUnit2_Framework_Test und stellt ebenso wie die Klasse PHPUnit2_Framework_TestCase die Zusicherungsmethoden der Klasse PHPUnit2_Framework_Assert bereit. Sie kann daher von jedem Verwender, der eigentlich ein Objekt der Klasse PHPUnit2_Framework_TestCase erwartet, verarbeitet werden.

Beispiel: Die Klasse PHPUnit2_Extensions_TestDecorator

  1. <?php
  2. require_once 'PHPUnit2/Framework/Assert.php';
  3. require_once 'PHPUnit2/Framework/Test.php';
  4. require_once 'PHPUnit2/Framework/TestResult.php';
  5.  
  6. class PHPUnit2_Extensions_TestDecorator
  7. extends PHPUnit2_Framework_Assert
  8. implements PHPUnit2_Framework_Test {
  9.   protected $test = NULL;
  10.  
  11.   public function
  12.   __construct(PHPUnit2_Framework_Test $test) {
  13.     $this->test = $test;
  14.   }
  15.  
  16.   public function toString() {
  17.     return $this->test->toString();
  18.   }
  19.  
  20.   public function
  21.   basicRun(PHPUnit2_Framework_TestResult $result) {
  22.     $this->test->run($result);
  23.   }
  24.  
  25.   public function countTestCases() {
  26.     return $this->test->countTestCases();
  27.   }
  28.  
  29.   protected function createResult() {
  30.     return new PHPUnit2_Framework_TestResult;
  31.   }
  32.  
  33.   public function getTest() {
  34.     return $this->test;
  35.   }
  36.  
  37.   public function
  38.   run(PHPUnit2_Framework_TestResult $result) {
  39.     $this->basicRun($result);
  40.     return $result;
  41.   }
  42. }
  43. ?>

Beispiel: Die Klasse PHPUnit2_Extensions_RepeatedTest

  1. <?php
  2. require_once 'PHPUnit2/Framework/Test.php';
  3. require_once 'PHPUnit2/Framework/TestResult.php';
  4. require_once 'PHPUnit2/Extensions/TestDecorator.php';
  5.  
  6. class PHPUnit2_Extensions_RepeatedTest
  7. extends PHPUnit2_Extensions_TestDecorator {
  8.   private $timesRepeat = 1;
  9.  
  10.   public function
  11.   __construct(PHPUnit2_Framework_Test $test,
  12.               $timesRepeat = 1) {
  13.       parent::__construct($test);
  14.  
  15.       if (is_integer($timesRepeat) &&
  16.           $timesRepeat >= 0) {
  17.           $this->timesRepeat = $timesRepeat;
  18.       } else {
  19.           throw new Exception('Illegal argument.');
  20.       }
  21.   }
  22.  
  23.   public function countTestCases() {
  24.       return $this->timesRepeat *
  25.              $this->test->countTestCases();
  26.   }
  27.  
  28.   public function
  29.   run(PHPUnit2_Framework_TestResult $result) {
  30.     for ($i = 0;
  31.          $i < $this->timesRepeat && !$result->shouldStop();
  32.          $i++) {
  33.       $this->test->run($result);
  34.     }
  35.  
  36.     return $result;
  37.   }
  38. }
  39. ?>

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 PHPUnit2_Extensions_RepeatedTest) übergeben, um ein entsprechendes Dekorierer-Objekt zu erzeugen, das dann für die Testausführung mit PHPUnit2_Framework_TestResult verwendet werden kann.

Beispiel: Wiederholte Testausführung mit PHPUnit2_Extensions_RepeatedTest

  1. <?php
  2. require_once 'PHPUnit2/Framework/TestResult.php';
  3. require_once 'PHPUnit2/Extensions/RepeatedTest.php';
  4.  
  5. require_once 'SampleTest.php';
  6.  
  7. $result = new PHPUnit2_Framework_TestResult;
  8.  
  9. // Führt SampleTest::testDoSomething() einmal aus.
  10. $test = new SampleTest('testDoSomething');
  11. $result->run($test);
  12.  
  13. // Führt SampleTest::testDoSomething() einmal aus.
  14. $decoratedTest = new PHPUnit2_Extensions_RepeatedTest($test, 2);
  15. $result->run($decoratedTest);
  16. ?>

(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-Muster

Das 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

  1. <?php
  2. class Example
  3. {
  4.    // The factory method
  5.    public static function factory($type)
  6.    {
  7.        if (include_once 'Drivers/' . $type . '.php') {
  8.            $classname = 'Driver_' . $type;
  9.            return new $classname;
  10.        } else {
  11.            throw new Exception ('Driver not found');
  12.        }
  13.    }
  14. }
  15. ?>

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:

  1. <?php
  2. // Load a MySQL Driver
  3. $mysql = Example::factory('MySQL');
  4.  
  5. // Load a SQLite Driver
  6. $sqlite = Example::factory('SQLite');
  7. ?>

(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:

  1. class Meine_Klasse
  2. {
  3.     static public function factory($className, $params = null)
  4.     {
  5.         if (! is_string($className) || ! strlen($className)) {
  6.             throw new exception(
  7.                 'Die zu ladende Klasse muss in einer Zeichenkette benannt werden');
  8.         }
  9.  
  10.         require_once $className . '.php';
  11.         return new $className($params);
  12.     }
  13. }
  14.  
  15. $params = array(
  16.     'param1' => null,
  17.     'param2' => null,
  18. );
  19.  
  20. $object = Meine_Klasse::factory('test_klasse_konkret', $params);

In diesem Beispiel ist MDB2::connect() die factory-Methode und liefert ein Datenbank-Verbindungs-Objekt oder im Fehlerfall ein PEAR-Error-Objekt zurück.

  1. require_once 'MDB2.php';
  2.  
  3. $dsn = 'pgsql://someuser:apasswd@localhost/thedb';
  4. $options = array(
  5.     'debug' => 2,
  6.     'result_buffering' => false,
  7. );
  8.  
  9. $mdb2 = MDB2::connect($dsn, $options);
  10. if (PEAR::isError($mdb2)) {
  11.     die($mdb2->getMessage());
  12. }
  13.  
  14. // ...
  15.  
  16. $mdb2->disconnect();

(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 Factory

Problem

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

  1. <?php
  2. switch ($type) {
  3.   case 'HTTP': {
  4.     include_once 'partner_interface/http.php';
  5.     $interface = new PartnerInterface_HTTP;
  6.   }
  7.   break;
  8.  
  9.   case 'SOAP': {
  10.     include_once 'partner_interface/soap.php';
  11.     $interface = new PartnerInterface_SOAP;
  12.   }
  13.   break;
  14.  
  15.   case 'XML-RPC': {
  16.     include_once 'partner_interface/xml-rpc.php';
  17.     $interface = new PartnerInterface_XMLRPC;
  18.   }
  19.   break;
  20. }
  21. ?>

Nachdem die Auswahl des zu erzeugenden Objektes in der Methode factory() der Klasse PartnerInterface an zentraler Stelle gekapselt wurde, gestaltet sich die Erzeugung des passenden Objektes im Kontext des Programmes einfach und flexibel, wie das folgende Beispiel zeigt.

Beispiel: Verwendung der drei Klassen mit abstrakter Fabrik

  1. <?php
  2. $interface = PartnerInterface::factory($type);
  3. ?>

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

  1. <?php
  2. abstract class PartnerInterface {
  3.   protected function __construct() {}
  4.  
  5.   public static function factory($type) {
  6.     $source = 'PartnerInterface/' . $type . '.php';
  7.  
  8.     if (@require_once($source)) {
  9.       $class  = 'PartnerInterface_' . $type;
  10.       $object = new $class;
  11.  
  12.       return $object;
  13.     } else {
  14.       throw new Exception(
  15.         sprintf(
  16.           'Konnte kein Objekt vom Typ %s erzeugen.',
  17.           'PartnerInterface_' . $type
  18.         )
  19.       );
  20.     }
  21.   }
  22.  
  23.   public abstract function import($data);
  24.   public abstract function export();
  25. }
  26. ?>

Beispiel: Die konkrete Klasse PartnerInterface_HTTP

  1. <?php
  2. require_once 'PartnerInterface.php';
  3.  
  4. class PartnerInterface_HTTP extends PartnerInterface {
  5.   public function import($data) {
  6.     // ...
  7.   }
  8.  
  9.   public function export() {
  10.     // ...
  11.   }
  12. }
  13. ?>

(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-Muster

Problem

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 __clone() als private final unterbunden.

Für die Objekterzeugung wird eine statische Methode, meist getInstance() oder singleton() genannt, bereitgestellt. In dieser Methode kann nun entschieden werden, ob ein neues Objekt der Klasse erzeugt wird oder ob eine Referenz auf ein bereits erzeugtes Objekt zurückgegeben werden soll.

Abbildung: Struktur des Singleton-Patterns

Beispiel: Singleton

  1. <?php
  2. class Singleton {
  3.   private static $uniqueInstance = NULL;
  4.  
  5.   protected function __construct() {
  6.     print "Neues Objekt wird erzeugt.\n";
  7.   }
  8.  
  9.   private final function __clone() {}
  10.  
  11.   public static function getInstance() {
  12.     if (self::$uniqueInstance === NULL) {
  13.       self::$uniqueInstance = new Singleton;
  14.     }
  15.  
  16.     return self::$uniqueInstance;
  17.   }
  18. }
  19.  
  20. $a = Singleton::getInstance();
  21. $b = Singleton::getInstance();
  22.  
  23. if ($a === $b) {
  24.     print '$a und $b referenzieren dasselbe Objekt.'."\n";
  25. }
  26. ?>
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

  1. <?php
  2. class Example
  3. {
  4.    // Hold an instance of the class
  5.    private static $instance;
  6.  
  7.    // A private constructor; prevents direct creation of object
  8.    private function __construct()
  9.    {
  10.        echo 'I am constructed';
  11.    }
  12.  
  13.    // The singleton method
  14.    public static function singleton()
  15.    {
  16.        if (!isset(self::$instance)) {
  17.            $c = __CLASS__;
  18.            self::$instance = new $c;
  19.        }
  20.  
  21.        return self::$instance;
  22.    }
  23.  
  24.    // Example method
  25.    public function bark()
  26.    {
  27.        echo 'Woof!';
  28.    }
  29.  
  30.    // Prevent users to clone the instance
  31.    public function __clone()
  32.    {
  33.        trigger_error('Clone is not allowed.', E_USER_ERROR);
  34.    }
  35.  
  36. }
  37.  
  38. ?>

Dies erlaubt eine einzige Instanz der Klasse Example aufzurufen.

  1. <?php
  2. // This would fail because the constructor is private
  3. $test = new Example;
  4.  
  5. // This will always retrieve a single instance of the class
  6. $test = Example::singleton();
  7. $test->bark();
  8.  
  9. // This will issue an E_USER_ERROR.
  10. $test_clone = clone($test);
  11.  
  12. ?>

(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:

  1. error_reporting(E_ALL|E_STRICT)
  2. ini_set('display_errors', true);
  3.  
  4. abstract class Singleton
  5. { 
  6.     private static $instances = array();
  7.  
  8.     final public static function getInstance() 
  9.     { 
  10.         $class = get_called_class();
  11.         if (empty(self::$instances[$class])) {
  12.             $rc = new ReflectionClass($class);
  13.             self::$instances[$class] = $rc->newInstanceArgs(func_get_args());
  14.         }
  15.         return self::$instances[$class];
  16.     } 
  17.  
  18.     protected function __construct()
  19.     {}
  20.  
  21.     final private function __clone()
  22.     {}
  23. } 
  24.  
  25. class ConcreteSingleton extends Singleton 
  26. {
  27.     protected function __construct($string, Array $array)
  28.     {
  29.         echo __METHOD__ . '(' . $string . ', ' . print_r($array, true) . ')';
  30.     }
  31. } 
  32.  
  33. $test = ConcreteSingleton::getInstance('Hello World', array(1,2,3));

(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-Muster

Problem

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 PHPUnit2_Extensions_CodeCoverage_Renderer

  1. <?php
  2. abstract class PHPUnit2_Extensions_CodeCoverage_Renderer {
  3.   protected $codeCoverageInformation;
  4.  
  5.   protected function __construct($codeCoverageInformation) {
  6.     $this->codeCoverageInformation = $codeCoverageInformation;
  7.   }
  8.  
  9.   public function factory($type, $codeCoverageInformation) {
  10.     $class = 'PHPUnit2_Extensions_CodeCoverage_Renderer_' .
  11.              $type;
  12.  
  13.     $source = 'PHPUnit2/Extensions/CodeCoverage/Renderer/' .
  14.               $type . '.php';
  15.  
  16.     if (@require_once($source)) {
  17.         $object = new $class($codeCoverageInformation);
  18.  
  19.       return $object;
  20.     } else {
  21.       throw new Exception(
  22.         sprintf(
  23.           'Could not load class %s.',
  24.           $class
  25.         )
  26.       );
  27.     }
  28.   }
  29.  
  30.   public final function render() {
  31.     $buffer = '';
  32.  
  33.     foreach ($this->codeCoverageInformation as
  34.              $testCaseName => $sourceFiles) {
  35.       $buffer .= $this->startTestCase($testCaseName);
  36.  
  37.       foreach ($sourceFiles as
  38.                $sourceFile => $executedLines) {
  39.         $buffer .= $this->startSourceFile($sourceFile);
  40.  
  41.         $buffer .= $this->renderSourceFile(
  42.           file($sourceFile),
  43.           $executedLines
  44.         );
  45.  
  46.         $buffer .= $this->endSourceFile($sourceFile);
  47.       }
  48.  
  49.       $buffer .= $this->endTestCase($testCaseName);
  50.     }
  51.  
  52.     return $buffer;
  53.   }
  54.  
  55.   protected function startTestCase($testCaseName) {
  56.   }
  57.  
  58.   protected function endTestCase($testCaseName) {
  59.   }
  60.  
  61.   protected function startSourceFile($sourceFile) {
  62.   }
  63.  
  64.   protected function endSourceFile($sourceFile) {
  65.   }
  66.  
  67.   abstract protected function
  68.   renderSourceFile($codeLines, $executedLines);
  69. }
  70. ?>

Beispiel: Die Klasse PHPUnit2_Extensions_CodeCoverage_Renderer_Text

  1. <?php
  2. require_once 'PHPUnit2/Util/CodeCoverage/Renderer.php';
  3.  
  4. class PHPUnit2_Extensions_CodeCoverage_Renderer_Text
  5. extends PHPUnit2_Extensions_CodeCoverage_Renderer {
  6.   protected function startTestCase($testCaseName) {
  7.     return $testCaseName . "\n\n";
  8.   }
  9.  
  10.   protected function endTestCase($testCaseName) {
  11.     return "\n";
  12.   }
  13.  
  14.   protected function startSourceFile($sourceFile) {
  15.     return '  ' . $sourceFile . "\n\n";
  16.   }
  17.  
  18.   protected function endSourceFile($sourceFile) {
  19.     return "\n";
  20.   }
  21.  
  22.   protected function renderSourceFile($codeLines, $executedLines) {
  23.     $buffer = '';
  24.     $line   = 1;
  25.  
  26.     foreach ($codeLines as $codeLine) {
  27.       $buffer .= sprintf(
  28.         '    %4u|%4s| %s',
  29.  
  30.         $line,
  31.         (isset($executedLines[$line])) ? $executedLines[$line] . 'x' : '',
  32.         $codeLine
  33.       );
  34.  
  35.       $line++;
  36.     }
  37.  
  38.     return $buffer;
  39.   }
  40. }
  41. ?>

(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 Muster

Problem

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])

Einleitung

Das 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 for()-Schleife. Hierbei musste der Programmierer auf die Indexgrenzen achten.

Beispiel: Iterieren von Arrays in PHP 3

  1. <?php
  2. $a = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  3.  
  4. for ($i = 0; $i < 10; $i++) {
  5.   print $a[$i] . ' ';
  6. }
  7. ?>

1 2 3 4 5 6 7 8 9 10

Assoziative Arrays mussten in PHP 3 mit reset(), while(), list() und each() verarbeitet werden.

Beispiel: Iterieren von assoziativen Arrays in PHP 3

  1. <?php
  2. $a = array('key' => 'value');
  3.  
  4. reset($a);
  5.  
  6. while (list($k, $v) = each($a)) {
  7.   print $k . ': ' . $v;
  8. }
  9. ?>

key: value

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. <?php
  2. $a = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  3. $b = array('key' => 'value');
  4.  
  5. foreach ($a as $v) {
  6.   print $v . ' ';
  7. }
  8.  
  9. foreach ($b as $k => $v) {
  10.   print $k . ': ' . $v;
  11. }
  12. ?>
1 2 3 4 5 6 7 8 9 10
key: value

Bereits in PHP 4 war es möglich, den foreach-Operator auf ein Objekt anzuwenden. In diesem Fall wurden die Instanzvariablen des Objektes wie ein assoziatives Array durchlaufen (nächstes Beispiel). Dies ist in PHP 5 weiterhin möglich, jedoch werden hierbei nur die öffentlichen Instanzvariablen berücksichtigt.

Beispiel: Verwendung von foreach() mit Objekten

  1. <?php
  2. class Test {
  3.   var $a = 1;
  4.   var $b = 2;
  5. }
  6.  
  7. $test = new Test;
  8.  
  9. foreach ($test as $name => $value) {
  10.   print "$name: $value\n";
  11. }
  12. ?>
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 5

PHP bietet mit den Schnittstellen Iterator und IteratorAggregate eine Integration des Iterator-Entwurfsmusters in die Programmiersprache selbst: Objekte, die die Schnittstelle Iterator anbieten, können mit dem foreach-Operator verwendet werden.

Abbildung: Die Schnittstellen Iterator, IteratorAggregate und Traversable

Wendet man den foreach-Operator auf ein Objekt an, das die Schnittstelle Iterator anbietet, so bedient sich der foreach-Operator der folgenden Methoden, um an die Daten des Objektes zu gelangen:

  • rewind() setzt den Iterator zurück auf das erste Element der Menge.
  • valid() prüft, ob nach einem Aufruf von rewind() oder next() ein aktuelles Element existiert.
  • key() liefert den Schlüssel des aktuellen Elementes.
  • current() liefert den Wert des aktuellen Elementes.
  • next() setzt den Iterator auf das nächste Element der Menge.

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

  1. <?php
  2. class StringIterator implements Iterator {
  3.   private $string;
  4.   private $position;
  5.  
  6.   public function __construct($string) {
  7.     $this->string = explode(' ', $string);
  8.   }
  9.  
  10.   public function rewind() {
  11.     $this->position = 0;
  12.   }
  13.  
  14.   public function valid() {
  15.     return $this->position < sizeof($this->string);
  16.   }
  17.  
  18.   public function key() {
  19.     return $this->position;
  20.   }
  21.  
  22.   public function current() {
  23.     return $this->string[$this->position];
  24.   }
  25.  
  26.   public function next() {
  27.     $this->position++;
  28.   }
  29. }
  30. ?>

Beispiel: Implizite Verwendung von Iteratoren mit dem foreach-Operator

  1. <?php
  2. require_once 'StringIterator.php';
  3.  
  4. $iterator = new StringIterator('Dies ist ein String.');
  5.  
  6. foreach ($iterator as $key => $value) {
  7.   print $value . ' ';
  8. }
  9. ?>

Dies ist ein String.

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

  1. <?php
  2. require_once 'StringIterator.php';
  3.  
  4. $iterator = new StringIterator('Dies ist ein String.');
  5.  
  6. for ($iterator->rewind();
  7.      $iterator->valid();
  8.      $iterator->next()) {
  9.   $key   = $iterator->key();
  10.   $value = $iterator->current();
  11.  
  12.   print $value . ' ';
  13. }
  14. ?>

Dies ist ein String.

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 IteratorAggregate implementiert. Wird ein Objekt dieser Klasse zusammen mit dem foreach-Operator verwendet, so wird das String-Objekt unter Verwendung eines StringIterator-Objektes durchlaufen, ohne dass dieses von Hand erzeugt werden muss.

Beispiel: Eine Implementierung der Schnittstelle IteratorAggregate

  1. <?php
  2. require_once 'StringIterator.php';
  3.  
  4. class String implements IteratorAggregate {
  5.   private $string;
  6.  
  7.   public function __construct($string = '') {
  8.     $this->string = $string;
  9.   }
  10.  
  11.   public function getIterator() {
  12.     return new StringIterator($this->string);
  13.   }
  14.  
  15.   // ...
  16. }
  17.  
  18. $string = new String('Dies ist ein String.');
  19.  
  20. foreach ($string as $key => $value) {
  21.   print $value . ' ';
  22. }
  23. ?>

Dies ist ein String.

(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:

  • FilterIterator, LimitIterator und SeekableIterator bieten Funktionen für das Filtern, Einschränken und Suchen von Elementen einer Menge. Sie bilden den Kern der Standard PHP Library.
  • ArrayObject und ArrayIterator bieten eine alternative Möglichkeit für die Arbeit mit Arrays an.
  • RecursiveIterator, RecursiveIteratorIterator und ParentIterator stellen die Funktionalität zur Verfügung, um rekursiv mit verschachtelten Iteratoren arbeiten zu können.
  • RecursiveIterator ist hierbei eine Schnittstelle, die von einer Iterator-Klasse implementiert werden muss, wenn ihre Objekte von einem RecursiveIteratorIterator rekursiv verarbeitet werden sollen. Ein Beispiel für eine Klasse, die RecursiveIterator implementiert, ist SimpleXMLIterator.
  • DirectoryIterator und RecursiveDirectoryIterator bieten eine einfache und effiziente Möglichkeit, über die Dateien und Unterverzeichnisse eines Verzeichnisses im Dateisystem zu iterieren.
  • CachingIterator und CachingRecursiveIterator erweitern das Standardverhalten von Iterator-Implementierungen, indem sie immer ein Element im Voraus lesen.

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

  1. <?php
  2. $string = new String('Foo Bar Barbara');
  3.  
  4. foreach ($string as $key => $value) {
  5.   if (strpos($value, 'Bar') === 0) {
  6.     print $value . "\n";
  7.   }
  8. }
  9. ?>
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 accept() zu implementieren. In dieser Methode kann über $this->getInnerIterator()->current() auf das aktuelle Element des inneren Iterators zugegriffen werden. Soll dieses akzeptiert (also an den Verwender des äußeren Iterators weitergereicht) werden, so muss die Methode TRUE als Ergebnis liefern.

Das nächste Beispiel zeigt eine von FilterIterator abgeleitete Klasse, die den aus dem letzten Beispiel bekannten Filter implementiert.

Beispiel: Eine FilterIterator-Implementierung

  1. <?php
  2. require_once 'StringIterator.php';
  3.  
  4. class StringFilterIterator extends FilterIterator {
  5.   private $prefix;
  6.  
  7.   public function
  8.   __construct(StringIterator $stringIterator, $prefix) {
  9.     parent::__construct($stringIterator);
  10.     $this->prefix = $prefix;
  11.   }
  12.  
  13.   public function accept() {
  14.     $current = $this->getInnerIterator()->current();
  15.  
  16.     if (strpos($current, $this->prefix) === 0) {
  17.       return TRUE;
  18.     }
  19.  
  20.     return FALSE;
  21.   }
  22. }
  23.  
  24. $stringIterator = new StringIterator('Foo Bar Barbara');
  25. $filterIterator = new StringFilterIterator(
  26.   $stringIterator,
  27.   'Bar'
  28. );
  29.  
  30. foreach ($filterIterator as $key => $value) {
  31.     print "$value\n";
  32. }
  33. ?>
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 $offset und $count. Der Erste gibt die Nummer des ersten Elements an (beginnend bei 0), das akzeptiert werden soll. Der Zweite die maximale Anzahl an Elementen. Im folgenden Beispiel werden so alle Elemente ab einschließlich des Dritten ausgegeben.

Beispiel: Verwendung der Klasse LimitIterator

  1. <?php
  2. require_once 'StringIterator.php';
  3.  
  4. $stringIterator = new StringIterator('Foo Bar Barbara');
  5. $limitIterator  = new LimitIterator($stringIterator, 2);
  6.  
  7. foreach ($limitIterator as $key => $value) {
  8.   print "$value\n";
  9. }
  10. ?>

Barbara

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 FilterIterator und LimitIterator

  1. <?php
  2. require_once 'StringFilterIterator.php';
  3.  
  4. $stringIterator = new StringIterator('Foo Bar Barbara');
  5. $filterIterator = new StringFilterIterator($stringIterator, 'Bar');
  6. $limitIterator  = new LimitIterator($filterIterator, 1, 1);
  7.  
  8. foreach ($limitIterator as $key => $value) {
  9.   print $value . "\n";
  10. }
  11. ?>@
  12.  
  13. [@Barbara

Im Standardfall nutzt ein LimitIterator-Objekt wiederholte Aufrufe der Methode next(), die von der Schnittstelle Iterator vereinbart wird, um den durch die Parameter $offset und $count angegebenen Elementebereich zu erreichen. Bei der Verarbeitung der Ergebniszeilen einer Datenbankabfrage hat dieses Vorgehen jedoch einen Nachteil: Die vor dem gewünschten Bereich auftretenden Ergebniszeilen werden beispielsweise mit mysql_fetch_assoc() über die Verbindung zum Datenbankserver in den Speicher des PHP-Interpreters geladen, nur um gleich darauf unverarbeitet wieder überschrieben zu werden. Sinnvoller wäre an dieser Stelle die Verwendung der Funktion mysql_data_seek(), um direkt an die richtige Stelle der Ergebnismenge zu springen.

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 SeekableIterator implementiert. Als einzige Methode dieser Schnittstelle ist seek($position) zu implementieren. Ein Objekt einer solchen Klasse kann nun effizient an die gewünschte Position springen.

(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 ArrayAccess

Objekte von Klassen, die die Schnittstelle ArrayAccess (s. Abbildung) implementieren, können wie normale PHP-Arrays verwendet werden. Der Unterschied zu diesen ist jedoch, dass der Programmierer festlegen kann, was beispielsweise bei der Ausführung von $array[$offset] = $value passieren soll.

Abbildung: Die Schnittstelle ArrayAccess

Die vier zu implementierenden Methoden sind:

  • offsetExists($offset) wird für isset($array[$offset]) aufgerufen.
  • offsetGet($offset) wird für $value = $array[$offset] aufgerufen.
  • offsetSet($offset, $value) wird für $array[$offset] = $value aufgerufen. Hierbei ist zu beachten, dass bei Verwendung der $array[] = $value Syntax $offset den Wert NULL enthält.
  • offsetUnset($offset) wird für unset($array[$offset]) aufgerufen.

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

  1. <?php
  2. class PHPArray implements ArrayAccess {
  3.   private $array = array();
  4.  
  5.   public function offsetExists($offset) {
  6.     return array_key_exists($this->array, $offset);
  7.   }
  8.  
  9.   public function offsetGet($offset) {
  10.     return $this->array[$offset];
  11.   }
  12.  
  13.   public function offsetSet($offset, $value) {
  14.     if (is_null($offset)) {
  15.       $this->array[] = $value;
  16.     } else {
  17.       $this->array[$offset] = $value;
  18.     }
  19.   }
  20.  
  21.   public function offsetUnset($offset) {
  22.     unset($this->array[$offset]);
  23.   }
  24. }
  25.  
  26. $test = new PHPArray;
  27.  
  28. $test[] = 'Hello'; $test[] = ' World!';
  29. print $test[0] . $test[1];
  30. ?>

Hello World!

Nachdem wir uns mit der grundsätzlichen Verwendung der Schnittstelle ArrayAccess vertraut gemacht haben, kommen wir zu einer möglichen Verwendung.

Im folgenden Beispiel benutzen wir eine Implementierung der Schnittstelle ArrayAccess, um ein Array $_SHARED zu erzeugen, auf dessen Inhalt von unterschiedlichen PHP-Instanzen aus zugegriffen werden kann. Für die notwendige Datenhaltung benutzen wir die in PHP 5 eingebettete Datenbank SQLite. Mit Hilfe der ArrayAccess-Implementierung "verbergen" wir die Datenhaltung vor dem Verwender des Arrays.

Beispiel: Die Klasse SharedArray

  1. <?php
  2. class SharedArray implements ArrayAccess {
  3.   private $db = NULL;
  4.  
  5.   public function __construct() {
  6.     if ($this->db === NULL &&
  7.         $this->db = sqlite_open('shared_array.db')) {
  8.       @sqlite_query(
  9.         $this->db,
  10.         'CREATE TABLE shared_array
  11.          (offset varchar(32) PRIMARY KEY,
  12.           value  varchar(32));'
  13.       );
  14.     }
  15.   }
  16.  
  17.   public function __destruct() {
  18.     if ($this->db !== NULL) {
  19.       sqlite_close($this->db);
  20.     }
  21.  
  22.     $this->db = NULL;
  23.   }
  24.  
  25.   public function offsetExists($offset) {
  26.     $result = sqlite_query(
  27.       $this->db,
  28.       "SELECT offset
  29.          FROM shared_array
  30.         WHERE offset = '$offset';"
  31.     );
  32.  
  33.     if ($result === FALSE) {
  34.       return FALSE;
  35.     } else {
  36.       return TRUE;
  37.     }
  38.   }
  39.  
  40.   public function offsetGet($offset) {
  41.     return sqlite_fetch_single(
  42.       sqlite_query(
  43.         $this->db,
  44.         "SELECT value
  45.            FROM shared_array
  46.           WHERE offset = '$offset';"
  47.       )
  48.     );
  49.   }
  50.  
  51.   public function offsetSet($offset, $value) {
  52.     sqlite_query(
  53.       $this->db,
  54.       "REPLACE INTO shared_array
  55.                (offset, value)
  56.         VALUES ('$offset', '$value');"
  57.     );
  58.   }
  59.  
  60.   public function offsetUnset($offset) {
  61.     sqlite_query(
  62.       $this->db,
  63.       "DELETE FROM shared_array
  64.         WHERE offset = '$offset'"
  65.     );
  66.   }
  67. }
  68.  
  69. $_SHARED = new SharedArray;
  70. ?>

(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-Muster

Problem

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 TeureBerechnung und mit Beispiel eine entsprechende Implementierung.

Beispiel: Die Schnittstelle TeureBerechnung

  1. <?php
  2. interface TeureBerechnung {
  3.   public function rechne($a);
  4. }
  5. ?>

Beispiel: Die Klasse Beispiel

  1. <?php
  2. class Beispiel implements TeureBerechnung {
  3.   public function rechne($a) {
  4.     // ...
  5.   }
  6. }
  7. ?>

Die Klasse BeispielStellvertreter implementiert ebenfalls die Schnittstelle TeureBerechnung. Darüber hinaus bietet sie jedoch einen Caching-Mechanismus, der das Ergebnis der Methodenaufrufe von rechne($a) speichert und so die wiederholte Berechnung für gleiche Parameter $a verhindert.

Beispiel: Die Klasse BeispielStellvertreter

  1. <?php
  2. class BeispielStellvertreter implements TeureBerechnung {
  3.   private $cache = array();
  4.   private $objekt;
  5.  
  6.   public function __construct(TeureBerechnung $objekt) {
  7.     $this->objekt = $objekt;
  8.   }
  9.  
  10.   public function rechne($a) {
  11.     if (!isset($this->cache[$a])) {
  12.       $this->cache[$a] = $this->objekt->rechne($a);
  13.     }
  14.  
  15.     return $this->cache[$a];
  16.   }
  17. }
  18. ?>

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-Pattern

5.1  Außerirdische Spinnen

Ein 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  Gasfabrik

Eine 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  Gottobjekt

Ein 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-Effekt

Ein 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-Code

Eine sehr kompakte Systemstruktur, deren Kontrollfluss einem Topf Spaghetti ähnelt.

5.6  Sumo-Hochzeit

Ein 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  Zwiebel

Neue 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 & Paste

Der 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  Lavafluss

Beschreibt 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-Statement

Verhalten von Objekten gemäß ihrem Status (state) sollten mit Statusobjekten und dem state-Pattern gesteuert werden, nicht mit konditionalem Code.

5.11  Magic Values

Hierbei 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örter

Die 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])

Letzte Änderung am 23.08.2008 00:31 Uhr von chueser.