Es gibt viele Artikel, die erklären, was Designmuster sind und wie man sie umsetzt. Das Web braucht keinen weiteren Artikel! Stattdessen werden wir in diesem Artikel mehr über das Thema diskutieren wann und Warum, eher als das welche und Wie.
Ich werde verschiedene Situationen und Anwendungsfälle für Muster vorstellen und auch kurze Definitionen bereitstellen, um denjenigen von Ihnen zu helfen, die mit diesen spezifischen Mustern nicht so vertraut sind. Lass uns anfangen.
Erneut veröffentlichtes TutorialAlle paar Wochen besuchen wir einige der Lieblingsbeiträge unserer Leser aus der gesamten Geschichte der Website. Dieses Tutorial wurde erstmals im Oktober 2012 veröffentlicht.
Dieser Artikel behandelt einige der verschiedenen Aspekte Agile Designmuster, dokumentiert in den Büchern von Robert C. Martin. Diese Muster sind moderne Anpassungen der ursprünglichen Designmuster, die von definiert und dokumentiert werden Die Viererbande 1994. Martins Muster stellen eine viel neuere Sicht auf die GoF-Muster dar und sie arbeiten besser mit modernen Programmiertechniken und Problemen. Tatsächlich wurden etwa 15% der ursprünglichen Muster durch neuere Muster ersetzt und die übrigen Muster wurden geringfügig modernisiert.
Das Factory-Muster wurde entwickelt, um Programmierern bei der Organisation der Informationen zu helfen, die mit der Objekterstellung zusammenhängen. Objekte haben manchmal viele Konstruktorparameter. Andernfalls müssen sie unmittelbar nach ihrer Erstellung mit Standardinformationen gefüllt werden. Diese Objekte sollten in Fabriken erstellt werden und alle Informationen zu ihrer Erstellung und Initialisierung an einem Ort aufbewahren.
Wann Verwenden Sie ein Factory-Pattern, wenn Sie Code schreiben, um Informationen zu sammeln, die zum Erstellen von Objekten erforderlich sind.
Warum: Fabriken helfen dabei, die Logik der Objekterstellung an einem Ort einzuschränken. Sie können auch Abhängigkeiten aufheben, um lose Kopplung und Abhängigkeitseinspritzung zu erleichtern, um bessere Tests zu ermöglichen.
Es gibt zwei häufig verwendete Muster zum Abrufen von Informationen aus einer Persistenzschicht oder einer externen Datenquelle.
Dieses Muster definiert einen Kommunikationskanal zwischen einer Persistenzlösung und der Geschäftslogik. Bei einfacheren Anwendungen können ganze Objekte selbst abgerufen oder neu erstellt werden. Die Objekterstellung liegt jedoch in der Verantwortung der Fabriken in den meisten komplexen Anwendungen. Gateways rufen einfach Rohdaten ab und behalten diese.
Wann: Wenn Sie Informationen abrufen oder beibehalten müssen.
Warum: Es bietet eine einfache öffentliche Schnittstelle für komplizierte Persistenzoperationen. Es kapselt auch Persistenzwissen und entkoppelt die Geschäftslogik von der Persistenzlogik.
Tatsächlich ist das Gateway-Muster nur eine bestimmte Implementierung eines anderen Entwurfsmusters, das wir in Kürze besprechen werden: das Adaptermuster.
Es gibt Zeiten, in denen Sie das Wissen über die Persistenzschicht Ihren Geschäftsklassen nicht offenlegen können (oder wollen). Das Proxy-Muster ist eine gute Möglichkeit, Ihre Geschäftsklassen dahingehend zu täuschen, dass sie bereits vorhandene Objekte verwenden.
Wann: Sie müssen Informationen von einer Persistenzschicht oder einer externen Quelle abrufen, möchten jedoch nicht, dass Ihre Geschäftslogik dies weiß.
Warum: Einen unaufdringlichen Ansatz zum Erstellen von Objekten im Hintergrund bieten. Es eröffnet auch die Möglichkeit, diese Objekte schnell, faul und aus verschiedenen Quellen abzurufen.
Ein Proxy implementiert effektiv die gleiche Schnittstelle wie ein reales Objekt und ahmt dessen Funktionalität nach. Die Geschäftslogik verwendet es einfach so, als wäre es ein reales Objekt. Tatsächlich erstellt der Proxy das Objekt, wenn es nicht existiert.
Das aktive Objektmuster spielte auch in frühen Multi-Tasking-Systemen eine Rolle.
Okay okay. Das ist großartig und alles, aber wie können wir die Objekte finden, die wir erstellen müssen?
Das Repository-Muster ist sehr nützlich für die Implementierung von Suchmethoden und Mini-Abfragesprachen. Es nimmt diese Abfragen und verwendet ein Gateway, um die Daten für eine Fabrik abzurufen, um die benötigten Objekte zu erzeugen.
Das Repository-Muster unterscheidet sich von den anderen Mustern. Es existiert als Teil von Domain Driven Design (DDD) und ist nicht Bestandteil von Robert C. Martins Buch.
Wann: Sie müssen mehrere Objekte basierend auf Suchkriterien erstellen oder wenn Sie mehrere Objekte in der Persistenzschicht speichern müssen.
Warum: Clients, die bestimmte Objekte benötigen, können mit einer allgemeinen und gut isolierten Abfrage- und Persistenzsprache arbeiten. Es entfernt noch mehr Erstellungscode aus der Geschäftslogik.
Was aber, wenn das Repository die Objekte nicht finden kann? Eine Möglichkeit wäre die Rückgabe eines NULL
Wert, aber dies hat zwei Nebenwirkungen:
if (is_null ($ param)) return;
) in Ihrem Code.Ein besserer Ansatz ist, a zurückzugeben Null
Objekt.
Ein NULL-Objekt implementiert dieselbe Schnittstelle wie Ihre anderen Objekte, aber die Member des Objekts geben einen neutralen Wert zurück. Beispielsweise würde eine Methode, die eine Zeichenfolge zurückgibt, eine leere Zeichenfolge zurückgeben. Ein anderes Element, das einen numerischen Wert zurückgibt, gibt Null zurück. Dies zwingt Sie zur Implementierung von Methoden, die keine aussagekräftigen Daten zurückgeben. Sie können diese Objekte jedoch verwenden, ohne sich um abgelehnte Vermächtnisse zu sorgen oder Ihren Code mit Nullprüfungen zu verschwenden.
Wann: Sie überprüfen häufig Null
oder Sie haben Vermächtnisse abgelehnt.
Warum: Dies kann Ihrem Code Klarheit verleihen und Sie dazu zwingen, mehr über das Verhalten Ihrer Objekte nachzudenken.
Es ist nicht ungewöhnlich, viele Methoden für ein Objekt aufzurufen, bevor es seine Aufgabe erfüllen kann. Es gibt Situationen, in denen Sie ein Objekt nach seiner Erstellung vorbereiten müssen, bevor Sie es wirklich verwenden können. Dies führt zu Code-Duplizierung, wenn diese Objekte an verschiedenen Orten erstellt werden.
Wann: Wenn Sie viele Vorgänge ausführen müssen, um Objekte für die Verwendung vorzubereiten.
Warum: Um die Komplexität vom verbrauchenden Code zum erstellenden Code zu verschieben.
Das hört sich gut an, nicht wahr? Tatsächlich ist es in vielen Situationen sehr nützlich. Das Befehlsmuster wird häufig für die Implementierung von Transaktionen verwendet. Wenn Sie eine einfache hinzufügen rückgängig machen()
Methode zu einem Befehlsobjekt, kann es alle von ihm durchgeführten Rückgängig-Transaktionen verfolgen und sie gegebenenfalls rückgängig machen.
Jetzt haben Sie zehn (oder mehr) Befehlsobjekte und möchten, dass sie gleichzeitig ausgeführt werden. Sie können sie in einem aktiven Objekt sammeln.
Das einfache und interessante aktive Objekt hat nur eine Verantwortung: Führen Sie eine Liste mit Befehlsobjekten und führen Sie sie aus.
Wann: Mehrere ähnliche Objekte müssen mit einem einzigen Befehl ausgeführt werden.
Warum: Es zwingt Clients dazu, eine einzige Aufgabe auszuführen und mehrere Objekte zu beeinflussen.
Ein aktives Objekt entfernt jeden Befehl aus seiner Liste, nachdem der Befehl ausgeführt wurde. Das heißt, Sie können den Befehl nur einmal ausführen. Einige Beispiele aus der realen Welt eines aktiven Objekts sind:
Entwurfsmuster sollen Probleme lösen.
Kaufen()
Befehl für jedes Produkt entfernt sie aus dem Warenkorb.Das aktive Objektmuster spielte auch in frühen Multi-Tasking-Systemen eine Rolle. Jedes Objekt innerhalb eines aktiven Objekts würde einen Verweis auf das aktive Objekt beibehalten. Sie würden einen Teil ihrer Jobs ausführen und sich dann wieder in die Warteschlange stellen. Selbst in heutigen Systemen können Sie ein aktives Objekt verwenden, um andere Objekte arbeiten zu lassen, während Sie auf eine Antwort von einer anderen Anwendung warten.
Ich bin überzeugt, dass Sie das große Versprechen der objektorientierten Programmierung gehört haben: Wiederverwendung von Code. Frühe Anwender von OOP sahen die Verwendung von universellen Bibliotheken und Klassen in Millionen verschiedener Projekte vor. Nun, es ist nie passiert.
Dieses Muster ermöglicht die teilweise Wiederverwendung von Code. Dies ist praktisch bei mehreren Algorithmen, die sich nur geringfügig voneinander unterscheiden.
Wann: Beseitigen Sie Duplikate auf einfache Weise.
Warum: Es gibt Doppelarbeit und Flexibilität ist kein Problem.
Aber Flexibilität ist schön. Was ist, wenn ich es wirklich brauche??
Wann: Flexibilität und Wiederverwendbarkeit ist wichtiger als Einfachheit.
Warum: Verwenden Sie es, um große, austauschbare Blöcke komplizierter Logik zu implementieren, während Sie eine gemeinsame Algorithmus-Signatur beibehalten.
Sie können beispielsweise einen generischen Code erstellen Taschenrechner
und dann anders verwenden Berechnungsstrategie
Objekte, um die Berechnungen durchzuführen. Dies ist ein mäßig verwendetes Muster, und es ist am stärksten, wenn Sie viele bedingte Verhaltensweisen definieren müssen.
Mit zunehmenden Projekten wird es für externe Benutzer immer schwieriger, auf unsere Anwendung zuzugreifen. Dies ist ein Grund, der betreffenden Anwendung oder dem betreffenden Modul einen genau definierten Einstiegspunkt anzubieten. Andere solche Gründe können den Wunsch beinhalten, die interne Funktionsweise und Struktur des Moduls zu verbergen.
Eine Fassade ist im Wesentlichen eine API - eine nette und auf den Kunden ausgerichtete Schnittstelle. Wenn ein Client eine dieser schönen Methoden aufruft, delegiert die Fassade eine Reihe von Aufrufen an die Klassen, die sie versteckt, um dem Client die erforderlichen Informationen oder das gewünschte Ergebnis zu liefern.
Wann: Um Ihre API zu vereinfachen oder die innere Geschäftslogik absichtlich zu verbergen.
Warum: Sie können die API sowie die realen Implementierungen und Logik unabhängig voneinander steuern.
Die Kontrolle ist gut und oft müssen Sie eine Aufgabe ausführen, wenn sich etwas ändert. Benutzer müssen benachrichtigt werden, rote LEDs müssen blinken, ein Alarm muss ertönen… Sie haben die Idee.
Das beliebte Laravel-Gerüst nutzt das Fassadenmuster hervorragend aus.
Ein Nullobjekt implementiert dieselbe Schnittstelle wie Ihre anderen Objekte.
Das Beobachtermuster bietet eine einfache Möglichkeit, Objekte zu überwachen und bei sich ändernden Bedingungen Maßnahmen zu ergreifen. Es gibt zwei Arten von Beobachterimplementierungen:
Wann: Zur Bereitstellung eines Benachrichtigungssystems innerhalb Ihrer Geschäftslogik oder zur Außenwelt.
Warum: Das Muster bietet eine Möglichkeit, Ereignisse mit einer beliebigen Anzahl verschiedener Objekte zu kommunizieren.
Anwendungsfälle für dieses Muster sind E-Mail-Benachrichtigungen, Protokollierungs-Daemons oder Nachrichtensysteme. Natürlich gibt es im wirklichen Leben unzählige andere Möglichkeiten, es zu benutzen.
Das Beobachtermuster kann mit einem Vermittlermuster erweitert werden. Dieses Muster verwendet zwei Objekte als Parameter. Der Mediator abonniert sich selbst für den ersten Parameter. Wenn das beobachtete Objekt geändert wird, entscheidet der Mediator, was am zweiten Objekt zu tun ist.
Wann: Die betroffenen Objekte können die beobachteten Objekte nicht kennen.
Warum: Einen versteckten Mechanismus anbieten, um andere Objekte im System zu beeinflussen, wenn sich ein Objekt ändert.
In manchen Fällen benötigen Sie spezielle Objekte, die in Ihrer Anwendung eindeutig sind, und Sie möchten sicherstellen, dass alle Verbraucher Änderungen an diesen Objekten sehen können. Sie möchten auch das Erstellen mehrerer Instanzen solcher Objekte aus bestimmten Gründen verhindern, z. B. lange Initialisierungszeit oder Probleme mit gleichzeitigen Aktionen für einige Bibliotheken von Drittanbietern.
Ein Singleton ist ein Objekt mit einem privaten Konstruktor und einem öffentlichen bekomme Instanz()
Methode. Diese Methode stellt sicher, dass nur eine Instanz des Objekts vorhanden ist.
Wann: Sie müssen Singularität erreichen und möchten eine plattformübergreifende, lässig evaluierte Lösung, die auch die Möglichkeit der Erstellung durch Ableitung bietet.
Warum: Bei Bedarf einen zentralen Zugangspunkt bieten.
Eine andere Herangehensweise an Singularität ist das monostatische Designmuster. Diese Lösung verwendet einen Trick, der von objektorientierten Programmiersprachen angeboten wird. Es verfügt über dynamische öffentliche Methoden, die die Werte statischer privater Variablen abrufen oder festlegen. Dies stellt wiederum sicher, dass alle Instanzen solcher Klassen dieselben Werte haben.
Wann: Transparenz, Ableitbarkeit und Polymorphismus werden zusammen mit der Singularität bevorzugt.
Warum: Um vor den Benutzern / Kunden die Tatsache zu verbergen, dass das Objekt Singularität bietet.
Achten Sie besonders auf die Singularität. Es verschmutzt den globalen Namensraum und kann in den meisten Fällen durch etwas ersetzt werden, das für diese spezielle Situation besser geeignet ist.
Das Repository-Muster ist sehr nützlich für die Implementierung von Suchmethoden…
Du hast also einen Schalter und ein Licht. Der Schalter kann das Licht ein- und ausschalten, aber jetzt haben Sie einen Lüfter gekauft und möchten Ihren alten Schalter damit verwenden. Das ist in der physischen Welt leicht zu erreichen. Nimm den Schalter, schließe die Drähte und die Bratsche an.
Leider ist es in der Programmierwelt nicht so einfach. Du hast ein Schalter
Klasse und a Licht
Klasse. Wenn dein Schalter
verwendet die Licht
, wie könnte es das nutzen? Ventilator
?
Einfach! Kopieren Sie den Schalter
, und ändern Sie es, um die Ventilator
. Aber das ist Code-Duplizierung. es ist das Äquivalent des Kaufens eines anderen Schalters für den Lüfter. Sie könnten verlängern Schalter
zu FanSwitch
, und benutze stattdessen dieses Objekt. Aber was ist, wenn Sie eine verwenden möchten Taste
oder Fernbedienung
, anstelle einer Schalter
?
Dies ist das einfachste Muster, das jemals erfunden wurde. Es verwendet nur eine Schnittstelle. Das ist es, aber es gibt verschiedene Implementierungen.
Wann: Sie müssen Objekte verbinden und bleiben flexibel.
Warum: Denn es ist der einfachste Weg, um Flexibilität zu erreichen, wobei das Prinzip der Abhängigkeitsinversion und das Open-Close-Prinzip beachtet werden.
PHP ist dynamisch typisiert. Das bedeutet, dass Sie Schnittstellen auslassen und verschiedene Objekte im selben Kontext verwenden können - das Risiko einer abgelehnten Vererbung. PHP erlaubt jedoch auch die Definition von Schnittstellen, und ich empfehle Ihnen, diese großartige Funktionalität zu verwenden, um die Absicht Ihres Quellcodes klarer zu gestalten.
Aber Sie haben bereits eine Reihe von Klassen, mit denen Sie sprechen möchten? Ja bitte. Es gibt viele Bibliotheken, APIs von Drittanbietern und andere Module, mit denen man sprechen muss. Dies bedeutet jedoch nicht, dass unsere Geschäftslogik die Details solcher Dinge kennen muss.
Das Adaptermuster erstellt einfach eine Entsprechung zwischen der Geschäftslogik und etwas anderem. Wir haben bereits ein solches Muster in Aktion gesehen: das Gateway-Muster.
Wann: Sie müssen eine Verbindung mit einem bereits vorhandenen und möglicherweise sich ändernden Modul, einer Bibliothek oder einer API erstellen.
Warum: Damit sich Ihre Geschäftslogik nur auf die öffentlichen Methoden des Adapters verlassen kann und die andere Seite des Adapters problemlos geändert werden kann.
Wenn eines der oben genannten Muster nicht zu Ihrer Situation passt, können Sie Folgendes verwenden:
Dies ist ein sehr kompliziertes Muster. Ich persönlich mag es nicht, weil es normalerweise einfacher ist, einen anderen Ansatz zu wählen. Für andere Fälle, in denen andere Lösungen fehlschlagen, können Sie das Brückenmuster berücksichtigen.
Wann: Das Adaptermuster reicht nicht aus und Sie ändern die Klassen auf beiden Seiten der Rohrleitung.
Warum: Erhöhte Flexibilität auf Kosten beträchtlicher Komplexität bieten.
Beachten Sie, dass Sie über ein Skript mit ähnlichen Befehlen verfügen, und Sie möchten sie mit einem einzigen Aufruf ausführen. Warten! Haben wir so etwas nicht schon früher gesehen? Das aktive Objektmuster?
Ja, das haben wir getan. Aber das hier ist ein bisschen anders. Es ist das zusammengesetzte Muster und enthält wie das aktive Objektmuster eine Liste von Objekten. Das Aufrufen einer Methode für ein zusammengesetztes Objekt ruft jedoch die gleiche Methode für alle Objekte auf, ohne sie aus der Liste zu entfernen. Die Clients, die eine Methode aufrufen, denken, dass sie mit einem einzelnen Objekt dieses bestimmten Typs sprechen. Tatsächlich werden ihre Aktionen jedoch auf viele, viele Objekte desselben Typs angewendet.
Wann: Sie müssen eine Aktion auf mehrere ähnliche Objekte anwenden.
Warum: Reduzieren Sie die Duplizierung und vereinfachen Sie den Aufruf ähnlicher Objekte.
Hier ein Beispiel: Sie haben eine Anwendung, die erstellen und platzieren kann Aufträge
. Angenommen, Sie haben drei Aufträge: $ order1
, $ order2
und $ order3
. Sie könnten anrufen Platz()
auf jedem von ihnen, oder Sie könnten diese Aufträge in einem enthalten $ compositeOrder
Objekt, und nennen Sie es Platz()
Methode. Dies wiederum nennt die Platz()
Methode auf alle enthaltenen Auftrag
Objekte.
Gateways rufen nur Rohdaten ab und behalten diese.
Eine Finite-State-Machine (FSM) ist ein Modell mit einer begrenzten Anzahl von diskreten Zuständen. Die Implementierung eines FSM kann schwierig sein, und der einfachste Weg dazu ist das Vertrauen Schalter
Aussage. Jeder Fall
Die Anweisung repräsentiert einen aktuellen Status in der Maschine und weiß, wie der nächste Status aktiviert wird.
Aber das wissen wir alle Schaltergehäuse
Aussagen sind weniger wünschenswert, da sie ein unerwünschtes Auffächern unserer Objekte verursachen. Also vergiss das Schaltergehäuse
Anweisung und betrachten stattdessen das Zustandsmuster. Das Zustandsmuster besteht aus mehreren Objekten: einem Objekt zum Koordinieren von Dingen, einer Schnittstelle, die einen abstrakten Zustand darstellt, und dann mehreren Implementierungen - eine für jeden Zustand. Jeder Zustand weiß, welcher Zustand danach kommt, und der Staat kann das koordinierende Objekt benachrichtigen, um seinen neuen Zustand auf den nächsten in Reihe zu setzen.
Wann: FSM-ähnliche Logik muss implementiert werden.
Warum: Um die Probleme von a zu beseitigen Schaltergehäuse
Aussage, und um die Bedeutung jedes einzelnen Staates besser zu kapseln.
Ein Nahrungsmittelautomat könnte eine haben Main
Klasse, die einen Verweis auf a hat Zustand
Klasse. Mögliche Zustandsklassen könnten so aussehen: WaitingForCoin
, InsertedCoin
, Ausgewähltes Produkt
, Auf Bestätigung warten
, Lieferprodukt
, ReturningChange
. Jeder Status führt seinen Job aus und erstellt das nächste Statusobjekt, das an die Koordinatorklasse gesendet wird.
Es gibt Situationen, in denen Sie Klassen oder Module in einer Anwendung bereitstellen, und Sie können sie nicht ändern, ohne das System radikal zu beeinflussen. Gleichzeitig müssen Sie jedoch neue Funktionen hinzufügen, die Ihre Benutzer benötigen.
Das Dekorateur-Muster kann in diesen Situationen hilfreich sein. Es ist sehr einfach: Nehmen Sie die vorhandene Funktionalität und ergänzen Sie sie. Dies wird erreicht, indem die ursprüngliche Klasse erweitert und zur Laufzeit neue Funktionen bereitgestellt werden. Alte Clients verwenden das neue Objekt weiterhin wie alte, und neue Clients verwenden sowohl die alte als auch die neue Funktionalität.
Wann: Sie können alte Klassen nicht ändern, aber Sie müssen ein neues Verhalten oder einen neuen Status implementieren.
Warum: Es bietet eine unaufdringliche Möglichkeit, neue Funktionen hinzuzufügen.
Ein einfaches Beispiel ist das Drucken von Daten. Sie geben dem Benutzer einige Informationen als Nur-Text aus, möchten jedoch auch die Möglichkeit bieten, in HTML zu drucken. Das Dekorateur-Muster ist eine solche Lösung, mit der Sie beide Funktionen beibehalten können.
Wenn sich das Problem der Funktionserweiterung unterscheidet, haben Sie eine komplexe baumartige Struktur von Objekten, und Sie möchten vielen Knoten gleichzeitig Funktionalität hinzufügen. Eine einfache Iteration ist nicht möglich, aber ein Besucher ist möglicherweise eine praktikable Lösung. Der Nachteil ist jedoch, dass eine Besuchermusterimplementierung Änderungen an der alten Klasse erfordert, wenn sie nicht dazu gedacht ist, einen Besucher anzunehmen.
Wann: Ein Dekorateur ist nicht angebracht und einige zusätzliche Komplexität ist akzeptabel.
Warum: Ermöglichung eines organisierten Ansatzes zum Definieren der Funktionalität für mehrere Objekte, jedoch zum Preis höherer Komplexität.
Verwenden Sie Entwurfsmuster, um Ihre Probleme zu lösen, aber nur, wenn sie passen.
Entwurfsmuster helfen, Probleme zu lösen. Benennen Sie Ihre Klassen als Implementierungsempfehlung niemals nach den Mustern. Finden Sie stattdessen die richtigen Namen für die richtigen Abstraktionen. Dies hilft Ihnen, besser zu erkennen, wann Sie wirklich ein Muster brauchen, anstatt nur eines zu implementieren, weil Sie es können.
Einige können sagen, dass, wenn Sie Ihre Klasse nicht mit dem Namen des Musters benennen, andere Entwickler Schwierigkeiten haben werden, Ihren Code zu verstehen. Wenn ein Muster schwer zu erkennen ist, liegt das Problem in der Implementierung des Musters.
Verwenden Sie Entwurfsmuster, um Ihre Probleme zu lösen, aber nur, wenn sie passen. Missbrauch sie nicht. Sie werden feststellen, dass eine einfachere Lösung für ein kleines Problem zutrifft. Sie werden jedoch feststellen, dass Sie ein Muster erst benötigen, nachdem Sie einige andere Lösungen implementiert haben.
Wenn Sie mit dem Erstellen von Mustern noch nicht vertraut sind, hoffe ich, dass Ihnen dieser Artikel eine Vorstellung davon gibt, wie Muster in Ihren Anwendungen hilfreich sein können. Danke fürs Lesen!