Entwurfsmuster sind nur etwas für Java-Architekten – das haben Sie vielleicht schon immer gedacht. Tatsächlich sind Designmuster für jeden nützlich. Wenn diese Werkzeuge nicht den „Architekturastronauten“ vorbehalten sind, was sind sie dann? Warum sind sie in PHP-Anwendungen nützlich? In diesem Artikel werden diese Probleme erläutert.
Das Buch „Design Patterns“ führte Designmuster in die Software-Community ein. Die Autoren des Buches sind Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides Design (allgemein bekannt als „Gang of Four“). Die Kernkonzepte hinter den vorgestellten Entwurfsmustern sind sehr einfach. Nach Jahren der Softwareentwicklung haben Gamma und andere bestimmte Muster mit festen Designs entdeckt, ähnlich wie Architekten Häuser und Gebäude entwerfen und Vorlagen dafür entwickeln, wo ein Badezimmer sein sollte oder wie eine Küche gebaut werden sollte. Die Verwendung dieser Vorlagen oder Entwurfsmuster bedeutet, dass bessere Gebäude schneller entworfen werden können. Das gleiche Konzept gilt für Software.
Entwurfsmuster stellen nicht nur eine nützliche Möglichkeit dar, robuste Software schneller zu entwickeln, sondern bieten auch eine Möglichkeit, große Ideen in freundliche Worte zu fassen. Sie könnten beispielsweise sagen, dass Sie ein Nachrichtensystem schreiben, das eine lose Kopplung bereitstellt, oder Sie könnten sagen, dass Sie ein Muster namens Observer schreiben.
Es ist sehr schwierig, den Wert von Mustern anhand kleinerer Beispiele zu demonstrieren. Dies fühlt sich oft wie ein Overkill an, da Muster tatsächlich in großen Codebasen funktionieren. In diesem Artikel wird keine große Anwendung demonstriert, daher müssen Sie darüber nachdenken, wie Sie die Prinzipien des Beispiels in Ihrer eigenen großen Anwendung anwenden können – nicht den Code selbst, der in diesem Artikel demonstriert wird. Das bedeutet nicht, dass Sie in kleinen Anwendungen keine Muster verwenden sollten. Viele gute Anwendungen beginnen als kleine Anwendungen und entwickeln sich zu großen Anwendungen. Es gibt also keinen Grund, nicht auf solchen soliden Codierungspraktiken aufzubauen. Nachdem Sie nun die Entwurfsmuster verstanden haben und wissen, warum sie nützlich sind, werfen wir einen Blick auf die fünf häufig verwendeten Muster in PHP V5.
Fabrikmuster
Ursprünglich im Buch „Design Patterns“ enthalten, empfehlen viele Designmuster die Verwendung einer losen Kopplung. Um dieses Konzept zu verstehen, ist es am besten, über die beschwerliche Reise zu sprechen, die viele Entwickler bei der Arbeit an großen Systemen durchlaufen. Wenn Sie einen Teil des Codes ändern, können Probleme auftreten und es kann zu kaskadierenden Unterbrechungen in anderen Teilen des Systems kommen – Teile, von denen Sie früher dachten, dass sie überhaupt nichts miteinander zu tun haben.
Das Problem ist die enge Kopplung. Funktionen und Klassen in einem Teil des Systems hängen stark vom Verhalten und der Struktur von Funktionen und Klassen in anderen Teilen des Systems ab. Sie möchten eine Reihe von Mustern, die es diesen Klassen ermöglichen, miteinander zu kommunizieren, Sie möchten sie jedoch nicht eng miteinander verknüpfen, um eine Verzahnung zu vermeiden. In großen Systemen hängt viel Code von wenigen Schlüsselklassen ab. Wenn diese Klassen geändert werden müssen, kann es zu Schwierigkeiten kommen. Angenommen, Sie haben eine User-Klasse, die aus einer Datei liest. Sie möchten es in eine andere Klasse ändern, die aus der Datenbank liest, Ihr gesamter Code verweist jedoch auf die ursprüngliche Klasse, die aus der Datei liest. Zu diesem Zeitpunkt ist es sehr praktisch, den Werksmodus zu verwenden.
Factory-Pattern ist eine Klasse mit bestimmten Methoden, die Objekte für Sie erstellen. Sie können Factory-Klassen verwenden, um Objekte zu erstellen, ohne new direkt zu verwenden. Wenn Sie auf diese Weise den Typ des erstellten Objekts ändern möchten, müssen Sie nur die Fabrik ändern. Der gesamte Code, der diese Fabrik verwendet, wird automatisch geändert.
Listing 1 zeigt ein Beispiel einer Factory-Klasse. Die Serverseite der Gleichung besteht aus zwei Teilen: einer Datenbank und einer Reihe von PHP-Seiten, die es Ihnen ermöglichen, Feedback hinzuzufügen, eine Liste mit Feedback anzufordern und Artikel zu einem bestimmten Feedback zu erhalten.
Listing 1. Factory1.php
<?php Schnittstelle IUser { Funktion getName(); }
Klasse User implementiert IUser { öffentliche Funktion __construct( $id ) { }
öffentliche Funktion getName() { gib „Jack“ zurück; } }
Klasse UserFactory { öffentliche statische Funktion Create( $id ) { return new User( $id ); } }
$uo = UserFactory::Create( 1 ); echo( $uo->getName()."n" ); ?> |
Die IUser-Schnittstelle definiert, welche Aktionen ein Benutzerobjekt ausführen soll. Die Implementierung von IUser heißt User, und die UserFactory-Factory-Klasse erstellt IUser-Objekte. Diese Beziehung kann durch UML in Abbildung 1 dargestellt werden.
Abbildung 1. Factory-Klasse und die zugehörige IUser-Schnittstelle und Benutzerklasse |
Wenn Sie diesen Code mit dem PHP-Interpreter in der Befehlszeile ausführen, erhalten Sie die folgenden Ergebnisse:
Der Testcode fordert das Benutzerobjekt von der Factory an und gibt das Ergebnis der getName-Methode aus.
Es gibt eine Variante des Fabrikmusters, die Fabrikmethoden verwendet. Diese öffentlichen statischen Methoden in einer Klasse erstellen Objekte dieses Typs. Diese Methode ist nützlich, wenn es wichtig ist, Objekte dieses Typs zu erstellen. Angenommen, Sie müssen ein Objekt erstellen und dann eine Reihe von Eigenschaften festlegen. Diese Version des Factory-Musters kapselt den Prozess an einem einzigen Ort, sodass Sie keinen komplexen Initialisierungscode kopieren und in die gesamte Codebasis einfügen müssen. Listing 2 zeigt ein Beispiel für die Verwendung einer Factory-Methode.
Listing 2. Factory2.php
<?php Schnittstelle IUser { Funktion getName(); }
Klasse User implementiert IUser { öffentliche statische Funktion Load( $id ) { return new User( $id ); }
öffentliche statische Funktion Create( ) { return new User( null ); }
öffentliche Funktion __construct( $id ) { }
öffentliche Funktion getName() { gib „Jack“ zurück; } }
$uo = User::Load( 1 ); echo( $uo->getName()."n" ); ?> |
Dieser Code ist viel einfacher. Es gibt nur eine Schnittstelle IUser und eine Benutzerklasse, die diese Schnittstelle implementiert. Die User-Klasse verfügt über zwei statische Methoden zum Erstellen von Objekten. Diese Beziehung kann durch UML in Abbildung 2 dargestellt werden.
Abbildung 2. IUser-Schnittstelle und Benutzerklasse mit Factory-Methode |
Das Ausführen des Skripts in der Befehlszeile führt zu den gleichen Ergebnissen wie Listing 1:
Wie oben erwähnt, können solche Modi in kleineren Umgebungen manchmal übertrieben wirken. Es ist jedoch am besten, diese solide Form der Codierung zu erlernen, die Sie auf Projekte jeder Größe anwenden können.
Einzelelementmodus
Einige Anwendungsressourcen sind exklusiv, da es nur eine Ressource dieses Typs gibt. Beispielsweise sind Verbindungen zu einer Datenbank über ein Datenbank-Handle exklusiv. Sie möchten das Datenbank-Handle in Ihrer Anwendung gemeinsam nutzen, da es einen Mehraufwand verursacht, wenn die Verbindung geöffnet oder geschlossen bleibt, insbesondere beim Abrufen einer einzelnen Seite.
Der Einzelelementmodus erfüllt diese Anforderung. Wenn die Anwendung jeweils nur ein Objekt enthält, ist dieses Objekt ein Singleton. Der Code in Listing 3 zeigt ein einzelnes Datenbankverbindungselement in PHP V5.
Listing 3. Singleton.php
<?php require_once("DB.php");
Klasse DatabaseConnection { öffentliche statische Funktion get() { static $db = null; if ( $db == null ) $db = new DatabaseConnection(); return $db; }
private $_handle = null; private Funktion __construct() { $dsn = 'mysql://root:password@localhost/photos'; $this->_handle =& DB::Connect( $dsn, array() ); }
öffentliche Funktion handle() { return $this->_handle; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
Dieser Code zeigt eine einzelne Klasse mit dem Namen DatabaseConnection. Sie können keine eigene DatabaseConnection erstellen, da der Konstruktor privat ist. Mit der statischen Get-Methode können Sie jedoch nur ein DatabaseConnection-Objekt abrufen. Die UML für diesen Code ist in Abbildung 3 dargestellt.
Abbildung 3. Einzelnes Element der Datenbankverbindung |
Der beste Beweis ist, dass das von der Handle-Methode zurückgegebene Datenbank-Handle bei beiden Aufrufen dasselbe ist. Sie können den Code in der Befehlszeile ausführen, um dies zu beobachten.
% php singleton.php Handle = Objekt-ID #3 Handle = Objekt-ID #3 % |
Die beiden zurückgegebenen Handles sind dasselbe Objekt. Wenn Sie in Ihrer gesamten Anwendung ein einzelnes Datenbankverbindungselement verwenden, können Sie dasselbe Handle überall wiederverwenden.
Sie können globale Variablen zum Speichern von Datenbank-Handles verwenden, dieser Ansatz ist jedoch nur für kleinere Anwendungen geeignet. Vermeiden Sie in größeren Anwendungen die Verwendung globaler Variablen und verwenden Sie Objekte und Methoden für den Zugriff auf Ressourcen. Beobachtermuster
Das Observer-Muster bietet Ihnen eine weitere Möglichkeit, eine enge Kopplung zwischen Komponenten zu vermeiden. Das Muster ist sehr einfach: Ein Objekt macht sich selbst beobachtbar, indem es eine Methode hinzufügt, die es einem anderen Objekt, dem Beobachter, ermöglicht, sich selbst zu registrieren. Wenn sich ein beobachtbares Objekt ändert, sendet es Nachrichten an registrierte Beobachter. Diese Beobachter nutzen diese Informationen, um Operationen unabhängig vom beobachtbaren Objekt durchzuführen. Das Ergebnis ist, dass Objekte miteinander kommunizieren können, ohne verstehen zu müssen, warum. Ein einfaches Beispiel ist eine Liste von Benutzern im System. Der Code in Listing 4 zeigt eine Liste von Benutzern an und sendet eine Nachricht, wenn ein Benutzer hinzugefügt wird. Diese Liste kann über einen Protokollbeobachter beobachtet werden, der Nachrichten sendet, wenn ein Benutzer hinzugefügt wird.
Listing 4. Observer.php
<?php Schnittstelle IObserver { Funktion onChanged( $sender, $args ); }
Schnittstelle IObservable { Funktion addObserver( $observer ); }
Die Klasse UserList implementiert IObservable { private $_observers = array();
öffentliche Funktion addCustomer( $name ) { foreach( $this->_observers as $obs ) $obs->onChanged( $this, $name ); }
öffentliche Funktion addObserver( $observer ) { $this->_observers []= $observer; } }
Die Klasse UserListLogger implementiert IObserver { öffentliche Funktion onChanged( $sender, $args ) { echo( "'$args' zur Benutzerliste hinzugefügtn" ); } }
$ul = new UserList(); $ul->addObserver( new UserListLogger() ); $ul->addCustomer( "Jack" ); ?> |
Dieser Code definiert vier Elemente: zwei Schnittstellen und zwei Klassen. Die IObservable-Schnittstelle definiert Objekte, die beobachtet werden können, und UserList implementiert diese Schnittstelle, um sich selbst als beobachtbar zu registrieren. Die IObserver-Liste definiert, wie man ein Beobachter wird. UserListLogger implementiert die IObserver-Schnittstelle. Diese Elemente sind in der UML in Abbildung 4 dargestellt.
Abbildung 4. Beobachtbare Benutzerliste und Benutzerlisten-Ereignislogger |
Wenn Sie es über die Befehlszeile ausführen, sehen Sie die folgende Ausgabe:
% PHP-Beobachter.php „Jack“ zur Benutzerliste hinzugefügt % |
Der Testcode erstellt eine UserList und fügt ihr den UserListLogger-Beobachter hinzu. Fügen Sie dann einen Verbraucher hinzu und benachrichtigen Sie den UserListLogger über diese Änderung.
Es ist wichtig zu wissen, dass UserList nicht weiß, was der Logger tun wird. Möglicherweise gibt es einen oder mehrere Listener, die andere Vorgänge ausführen. Sie könnten beispielsweise einen Beobachter haben, der eine Nachricht an neue Benutzer sendet und sie im System willkommen heißt. Der Wert dieses Ansatzes besteht darin, dass UserList alle von ihm abhängigen Objekte ignoriert und sich hauptsächlich auf die Verwaltung der Benutzerliste und das Senden von Nachrichten konzentriert, wenn sich die Liste ändert.
Dieses Muster ist nicht auf Objekte im Speicher beschränkt. Es ist die Grundlage für datenbankgesteuerte Nachrichtenabfragesysteme, die in größeren Anwendungen eingesetzt werden. Befehlskettenmodus
Das Befehlskettenmuster basiert auf lose gekoppelten Themen, bei denen Nachrichten, Befehle, Anforderungen oder alles andere über eine Reihe von Handlern gesendet werden. Jeder Handler entscheidet selbst, ob er die Anfrage bearbeiten kann. Wenn dies möglich ist, wird die Anfrage verarbeitet und der Prozess gestoppt. Sie können Handler zum System hinzufügen oder daraus entfernen, ohne dass sich dies auf andere Handler auswirkt. Listing 5 zeigt ein Beispiel für dieses Muster.
Listing 5. Chain.php
<?php interfaceICommand { Funktion onCommand( $name, $args ); }
Klasse CommandChain { private $_commands = array();
öffentliche Funktion addCommand( $cmd ) { $this->_commands []= $cmd; }
öffentliche Funktion runCommand( $name, $args ) { foreach( $this->_commands as $cmd ) { if ( $cmd->onCommand( $name, $args ) ) zurückkehren; } } }
Die Klasse UserCommand implementiert ICommand { öffentliche Funktion onCommand( $name, $args ) { if ( $name != 'addUser' ) return false; echo( "UserCommand behandelt 'addUser'n" ); return true; } }
Die Klasse MailCommand implementiert ICommand { öffentliche Funktion onCommand( $name, $args ) { if ( $name != 'mail' ) return false; echo( "MailCommand verarbeitet 'Mail'n" ); return true; } }
$cc = new CommandChain(); $cc->addCommand( new UserCommand() ); $cc->addCommand( new MailCommand() ); $cc->runCommand( 'addUser', null ); $cc->runCommand( 'mail', null ); ?> |
Dieser Code definiert die CommandChain-Klasse, die eine Liste von ICommand-Objekten verwaltet. Beide Klassen können die ICommand-Schnittstelle implementieren – eine, die auf E-Mail-Anfragen reagiert, und die andere, die auf das Hinzufügen von Benutzern reagiert. Abbildung 5 zeigt die UML.
Abbildung 5. Befehlskette und die zugehörigen Befehle |
Wenn Sie ein Skript ausführen, das Testcode enthält, erhalten Sie die folgende Ausgabe:
% PHP-Kette.php UserCommand behandelt „addUser“ MailCommand verarbeitet „Mail“ % |
Der Code erstellt zunächst das CommandChain-Objekt und fügt ihm zwei Instanzen des Befehlsobjekts hinzu. Führen Sie dann zwei Befehle aus, um zu sehen, wer auf die Befehle reagiert hat. Wenn der Name des Befehls mit UserCommand oder MailCommand übereinstimmt, schlägt der Code fehl und es wird keine Aktion ausgeführt. Das Befehlskettenmuster ist bei der Erstellung einer skalierbaren Architektur zur Bearbeitung von Anfragen wertvoll und viele Probleme können damit gelöst werden. Strategiemuster
Das letzte Designmuster, das wir behandeln, ist das Strategiemuster. In diesem Muster werden Algorithmen aus komplexen Klassen extrahiert und können daher leicht ersetzt werden. Wenn Sie beispielsweise das Ranking von Seiten in Suchmaschinen ändern möchten, ist der Strategiemodus eine gute Wahl. Denken Sie an die Teile einer Suchmaschine – einen, der Seiten durchsucht, einen, der jede Seite bewertet, und einen anderen, der die Ergebnisse basierend auf dem Ranking sortiert. In komplexen Beispielen gehören diese Teile alle zur selben Klasse. Mithilfe des Strategiemusters können Sie den Anordnungsteil in eine andere Klasse einfügen, um die Anordnung der Seite zu ändern, ohne dass sich dies auf den restlichen Code der Suchmaschine auswirkt.
Als einfacheres Beispiel zeigt Listing 6 eine Benutzerlistenklasse, die eine Möglichkeit bietet, eine Gruppe von Benutzern basierend auf einer Reihe von Plug-and-Play-Richtlinien zu finden.
Listing 6. Strategy.php
<?php Schnittstelle IStrategy { Funktionsfilter( $record ); }
Die Klasse FindAfterStrategy implementiert IStrategy { privat $_name;
öffentliche Funktion __construct( $name ) { $this->_name = $name; }
öffentlicher Funktionsfilter ($record) { return strcmp( $this->_name, $record ) <= 0; } }
Die Klasse RandomStrategy implementiert IStrategy { öffentlicher Funktionsfilter ($record) { return rand( 0, 1 ) >= 0,5; } }
Klasse UserList { privat $_list = array();
öffentliche Funktion __construct( $names ) { if ( $names != null ) { foreach( $names as $name ) { $this->_list []= $name; } } }
öffentliche Funktion add( $name ) { $this->_list []= $name; }
öffentliche Funktion find( $filter ) { $recs = array(); foreach( $this->_list as $user ) { if ( $filter->filter( $user ) ) $recs []= $user; } return $recs; } }
$ul = new UserList( array( „Andy“, „Jack“, „Lori“, „Megan“ ) ); $f1 = $ul->find( new FindAfterStrategy( "J" ) ); print_r( $f1 );
$f2 = $ul->find( new RandomStrategy() ); print_r( $f2 ); ?> |
Abbildung 6. Benutzerliste und Richtlinie zur Auswahl von Benutzern |
Die UserList-Klasse ist ein Wrapper für ein Array von Namen. Es implementiert die Methode find, die eine von mehreren Strategien verwendet, um eine Teilmenge dieser Namen auszuwählen. Diese Strategien werden durch die IStrategy-Schnittstelle definiert, die über zwei Implementierungen verfügt: eine, die den Benutzer zufällig auswählt, und eine andere, die alle Namen nach dem angegebenen Namen auswählt. Wenn Sie den Testcode ausführen, erhalten Sie die folgende Ausgabe:
%php strategy.php Array ( [0] => Jack [1] =>Lori [2] =>Megan ) Array ( [0] => Andy [1] =>Megan ) % |
Der Testcode führt für beide Strategien dieselbe Benutzerliste aus und zeigt die Ergebnisse an. Im ersten Fall sucht die Strategie nach jedem Namen, der auf J folgt, sodass Sie Jack, Lori und Megan erhalten. Bei der zweiten Strategie werden Namen nach dem Zufallsprinzip ausgewählt, was jedes Mal zu unterschiedlichen Ergebnissen führt. In diesem Fall sind die Ergebnisse Andy und Megan.
Das Strategiemuster eignet sich ideal für komplexe Datenverwaltungssysteme oder Datenverarbeitungssysteme, die ein hohes Maß an Flexibilität bei der Filterung, Suche oder Verarbeitung von Daten erfordern.
Abschluss
In diesem Artikel werden nur einige der häufigsten Entwurfsmuster vorgestellt, die in PHP-Anwendungen verwendet werden. Weitere Designmuster werden im Buch Design Patterns demonstriert. Lassen Sie sich nicht von der Mystik der Architektur abschrecken. Muster sind eine wunderbare Idee, die in jeder Programmiersprache und auf jedem Kenntnisstand funktioniert.