Design Patterns - Elegante Lösungen für sich wiederholende Probleme

Design Patterns, kann man als die Kunst des Designens von objekt-orientierter Software bezeichnen. Die folgenden Design Patterns/ Techniken stammen aus dem Buch "Design Patterns" der Gang of Four.



Unified Modeling Language (UML)


Als Grundlage für Design Patterns, ist es von Vorteil die UML zu kennen.

Hier ein super Link zu Design Patterns. Und hier noch einer.



DESIGN PATTERNS


/////////////Behavioural Design Patterns/////////////

Hier geht es um die Interaktion und Kommunikation zwischen den Objekten

Memento Pattern

Wofür verwenden wir es?
Zum Beispiel für den Undo/Redo-Mechanismus eines Text-Editors oder Taschenrechners. Oder für einen Snapshot eines Benutzerprofils, das nach dem Abbruch durch den User wiederhergestellt werden soll.

Die Idee dahinter: Wir wollen eine systematische Möglichkeit haben, eine Momentaufnahme des internen Zustands eines bestimmten Objekts zu speichern, ohne diesen Zustand offenzulegen, um sie später wiederherstellen zu können.
Mit dem Memento-Pattern erfassen und externalisieren wir das Innere eines Objekts, ohne die Hülle zu verletzen. Damit kann das Objekt später in diesem Zustand wiederhergestellt werden.

Originator:
- erstellt ein Memento, um den internen Zustand zu speichern
- verwendet Mementos, um seinen Zustand wiederherzustellen.

Memento:
- speichert einen unveränderlichen Snapshot des internen Zustands vom Originator
- kann nur vom Originator abgerufen werden

CareTaker:
- speichert Mementos
- bearbeitet oder liest nie Mementos.

State Pattern

Wofür verwenden wir es?
Ein Beispiel einer Applikation, wäre eine Photoshop-Tool Palette. Der Mauszeiger ändert sich, je nach ausgewähltem Tool (Pipette, Farbeimer, Pinsel).

Durch das State Pattern ändert sich das Verhalten einer Klasse basierend auf ihrem Zustand. Im State Pattern erstellen wir Objekte, die verschiedene Zustände darstellen, und ein Kontextobjekt, dessen Verhalten sich ändert, wenn sich sein Zustandsobjekt ändert. Zum Beispiel durchläuft ein Eiswürfel in der Hitze verschiedene Zustände(States) von hart, über flüssig bis hin zu verdampft.

Iterator Pattern

Wofür verwenden wir es?
Zum Beispiel, wenn man im Webbrowser auf "zurück" geht, um den Verlauf aufzurufen.

Dieses Muster wird verwendet, um sequenziell auf die Elemente eines Sammlungsobjekts zuzugreifen, ohne die zugrunde liegende Darstellung kennen zu müssen. Wenn wir auf eine Sammlung von Objekten zugreifen möchten, ohne deren interne Darstellung offenzulegen. Wenn mehrere Durchläufe von Objekten in der Sammlung unterstützt werden müssen.

Strategy Pattern

Wofür verwenden wir es?
Zum Beispiel, wenn ein User Bilder(jpg, png, ...) hochlädt und diese dann filtern möchte(contrast, black&white...).

Im Strategy Pattern erstellen wir Objekte, die verschiedene Strategien darstellen, und ein Kontextobjekt, dessen Verhalten je nach Strategieobjekt variiert. Das Strategieobjekt ändert den Ausführungsalgorithmus des Kontextobjekts. Ein Strategy Pattern definiert eine Familie von Funktionalitäten, umschließt jede einzelne davon und macht sie austauschbar. Es wird verwendet, wenn wir verschiedene Variationen eines Algorithmus benötigen. Der Unterschied zum State Pattern ist, dass wir keinen Single State haben, sondern unterschiedliche Strategien verwenden.

Template Method Pattern

Wofür verwenden wir es?
Zum Beispiel bei einer Buchungskontrolle(Audit-Trail) in einer Banking-App. Wir definieren in der abstrakten Klasse die Methode:

execute(){ audit.Trail.record();
doExecute();
und modifizieren über konkrete Klassen(TransferMoney, GenerateReport) die Ausführungen, in der die Buchungskontrolle stattfinden soll.

Die sogenannte "Schablonenmethode" ruft abstrakte Methoden auf, die erst in den Unterklassen definiert werden. Diese Methoden werden auch als Einschubmethoden bezeichnet. Dadurch besteht die Möglichkeit, einzelne Schritte des Algorithmus zu verändern oder zu überschreiben, ohne dass die zu Grunde liegende Struktur des Algorithmus modifiziert werden muss.

Command Pattern

Wofür verwenden wir es?
Wir nutzen dieses Pattern, um den Sender vom Empfänger zu entkoppeln.

Mit dem Command Pattern können wir die "Seperation of Concerns" erzwingen, indem wir jede Aufgabe in eine eigene Klasse abstrahieren. Ein Vorteil dieses Entwurfsmusters besteht darin, dass es einfacher ist, neue Versionen von Aufgaben zu erstellen, z. B. Schaltflächen in einem Graphical User Interface Framework, die verschiedene Aktionen in ihren individuellen Applikationen ausführen sollen. Das Command Pattern stellt die objektorientierte Entsprechung zu Rückruffunktionen (callback functions) dar. Unser GUI-Framework soll für alle App-Entwickler funktionieren.

Undoable Command

Wofür verwenden wir es?

Es gibt Kommandos, die rückgängig gemacht werden können (Schrift: bold) und andere, die nicht rückgängig gemacht werden können (Zoom-in). Deshalb bietet es sich an zwei Interfaces einzuführen. Mit dem Undoable Command Pattern können wir dies tun.

Observer Pattern

Wofür verwenden wir es?
Zum Beispiel in einer Excel mit mehreren Reitern(Werte, Summe) und einem Diagramm. Wenn sich die Werte ändern, muss sowohl die Summe als auch das Diagramm verändert werden.

Das Observer Pattern(Beobachter) ist ein Entwurfsmuster, bei dem ein Objekt(Subjekt) eine Liste seiner abhängigen Objekte (Observer) verwaltet und sie automatisch über alle Zustandsänderungen benachrichtigt, normalerweise durch Aufrufen einer ihrer Methoden. Wir nutzen es, wenn sich der Status eines Objektes ändert und wir anderen Objekten dazu Bescheid geben müssen. Innerhalb des Observer Patterns gibt es verschiedene Styles (push & pull).

Mediator Pattern

Wofür verwenden wir es?
Dieses Pattern wird verwendet, um die Kommunikationskomplexität zwischen mehreren Objekten zu reduzieren.

Anstatt innerhalb jeder Klasse die direkte Kommunikation mit anderen Klassen (starkes Coupling) zu implementieren, wird ein Mediator-Objekt eingesetzt und jede Klasse kann dieses Mediator-Objekt aufrufen, um mit anderen zu kommunizieren. Dies kann das Coupling zwischen den Klassen lockern und die Entwicklung und Erweiterung des Programms erleichtern.

Chain of Responsibility Pattern

Wofür verwenden wir es?
Wenn wir zum Beispiel einen HTTP-Request über einen Webserver vornehmen und eine Pipeline/Kette an Objekten benötigen, um unsere Anfrage zu prozessuieren. Wenn die Authentifizierung erfolgreich ist, wird der Request weitergeben und wenn nicht, dann nicht.

Um zu vermeiden den Sender einer Anfrage an den Empfänger zu koppeln, geben wir mehr als einem Objekt die Möglichkeit, die Anfrage zu bearbeiten. Diese Objekte verketten wir, um die Anforderung entlang der Kette zu übergeben, bis ein Objekt sie verarbeitet.

Visitor Pattern

Wofür verwenden wir es?
Das Pattern ermöglicht das Hinzufügen neuer Funktionen, ohne vorhandene Klassen zu ändern. Es bietet eine Möglichkeit, einen Algorithmus einfach von einer Objektstruktur zu trennen.

Oft ist es schwierig, nicht miteinander verwandte Operationen in die Klassen einer Objektstruktur zu integrieren. Bei der Erweiterung um neue Operationen, müssen alle Klassen erweitert werden. Das Visitor-Pattern lagert die Operationen in externe Besucherklassen aus. Dazu müssen die zu besuchenden Klassen eine Schnittstelle zum Empfang eines Besuchers definieren.



/////////////Structural Design Patterns/////////////

Hier geht es um die Beziehungen zwischen Objekten

Composite Pattern

Wofür verwenden wir es?
Mit dem Composite Command Pattern können wir bspw. eine "Save All"-Funktion in einer Applikation für mehrere Tabs einbringen. Damit ermöglichen wir es, dass nicht jedes Tab einzeln gespeichert werden muss.

Ein anderes Beispiel wäre eine Hiearchie in Objekten z.B. in Powerpoint. Wir können Formen gruppieren und sie dann gleichzeitig bewegen, vergrößern usw. Das Composite Command beschreibt eine Gruppe von Objekten, die genauso behandelt werden wie eine einzelne Instanz desselben Objekttyps.

Adapter Pattern

Wofür verwenden wir es?
Zum Beispiel, um ein Foto mit Filtern zu versehen. Hinweis: In Java ist eine Composition flexibler, denn in Java gibt es keine multiple Vererbung.

Dieses Pattern wird verwendet, um bspw. ein Interface einer Klasse zu einer anderen Form zu konvertieren. Ein Adapter-Pattern fungiert als Verbindung zwischen zwei inkompatiblen Schnittstellen, die ansonsten nicht direkt verbunden werden können. Ein Adapter umschließt eine vorhandene Klasse mit einer neuen Schnittstelle, damit sie mit der Schnittstelle des Clients kompatibel wird.

Decorator Pattern

Wofür verwenden wir es?
Es wird bspw. verwendet, wenn eine "Stream"-Klasse erweitert werden soll, die eigentlich nur Daten in die Cloud hochlädt. In einigen Fällen sollen die Daten aber 'encrypted' werden und dazu verhilft uns das Decorator-Entwurfsmuster.

Das Decorator Pattern ist oft nützlich, um das Prinzip der Single Responsibility einzuhalten, da es die Aufteilung der Funktionalität auf Klassen mit eindeutigen Problembereichen ermöglicht. Die Verwendung von Decorator kann effizienter sein als die Unterklassenbildung, da das Verhalten eines Objekts verbessert werden kann, ohne ein völlig neues Objekt zu definieren.

Facade Pattern

Wofür verwenden wir es?
"Provide a simple interface to a complex system" - Das Fassadenmuster wird häufig verwendet, wenn eine Interaktion mit einer komplexen externen Bibliothek oder einem Dienst stattfindet. Es wird genutzt, um Coupling zu reduzieren.

In diesem Muster erstellen wir eine Klasse, um die Interaktionen mit der Third-Party-Library zu kapseln. Diese Klasse wird Fascade (Fassade) genannt. Dies ermöglicht es uns, dem Rest unserer Anwendung eine einfachere Schnittstelle bereitzustellen. Entwickler, die an anderen Teilen unseres Systems arbeiten, müssen die Bibliothek von Drittanbietern nicht lernen; Stattdessen müssen sie nur mit der von uns erstellten Fassadenklasse verbunden werden. Zum Beispiel bei einer mobilen App - "Send push-notifications to Users".

Flyweight Pattern

Wofür verwenden wir es?
Zum Beispiel bei GoogleMaps. Wenn zuviel Memory-Space für die Icons der Ortungs-Points verwendet werden würde, würde die App crashen. Deshalb bedient man sich dem Flyweight Pattern, das die Daten für die verschiedenen Points aus einer Schablone zieht.

Das Flyweight Pattern(Fliegengewicht) ist eine bewährte, wiederverwendbare Vorlage - es dient als eine Art Lösungsschablone. Dieses Pattern wird häufig verwendet, wenn wir ähnliche Daten für mehrere Objekte gruppieren möchten. Der Hauptzweck besteht darin, gemeinsame Daten für mehrere Objekte an einem einzigen Ort zu speichern und somit die Speichernutzung zu optimieren.

Bridge Pattern

Wofür verwenden wir es?
Beispielsweise bei einer Universal-Fernbedienung: Um zu vermeiden, dass wir für jede TV-Marke eine eigene Klasse/Repository bilden müssen, kann das Pattern die Brücke zwischen Feature und Implementation bilden und zwei separate Hierarchien zusammenbringen.

Dieses Pattern hilft uns, wenn wir verschiedene Datenbanktreiber für unseren Repository verwenden müssen. Um die Verwendung separater Repositorys oder Klassen für verschiedene Datenbanktreiber zu vermeiden, ist das Bridge-Pattern ein eleganter Weg. Wir können damit flexible Hiearchien bilden, die unabhängig voneinander wachsen können.

Proxy Pattern

Wofür verwenden wir es?
Zum Beispiel bei einer EBook-App mit integrierter Bibliothek all unserer Bücher. Wir wollen aber nur eins davon laden und nicht alle gleichzeitig. Um das Ebook nur zu laden, wenn wir es brauchen, initializieren wir das "private field" realEbook absichtlich nicht.

Das Proxy-Pattern bietet einen Ersatz/ Platzhalter für ein anderes Objekt, um den Zugriff darauf zu steuern. Wir können dieses Muster verwenden, um ein darstellbares Objekt zu erstellen, das den Zugriff auf ein anderes Objekt steuert, das möglicherweise weit entfernt, teuer zu erstellen/ zu sichern ist.



/////////////Creational Design Patterns/////////////

Hier geht es um die verschiedenen Wege, um Objekte zu kreiieren

Prototype Pattern

Wofür verwenden wir es?
Wenn wir beispielsweise eine Form (z.B. einen Kreis) in Powerpoint kopieren wollen.

Im Prototype Pattern werden neue Instanzen auf Grundlage von prototypischen Instanzen („Vorlagen“) erzeugt. Dabei wird die Vorlage kopiert und an neue Bedürfnisse angepasst.

Singleton Pattern

Wofür verwenden wir es?
Beispielsweise für eine Klasse, die die Konfigurationseinstellungen unserr Applikation managed.

Ein Singleton bedeutet (wie "single" schon sagt) die Beschränkung der Instanziierung einer Klasse auf eine einzelne Instanz. Mit anderen Worten, jedes Mal, wenn wir die Klasse verwenden, ist es jedoch dasselbe Objekt, das wir zuerst erstellt haben.

Dieses Muster ermöglicht es uns also, von jeder Datei oder Funktion aus auf die Mitglieder einer Klasseninstanz zuzugreifen und sie zu bearbeiten. Diese Singletons können Schnittstellen implementieren, Methoden als Argumente übergeben und können polymorph sein.

Factory Method Pattern

Wofür verwenden wir es?
Wenn wir z.B. ein eigenes Framework bauen und darin Produkte/Komponenten (Controller) vorbereiten, die später von Usern modifiziert (SonyController) werden können.

Dieses Pattern ist eine clevere, aber subtile Erweiterung des Konzepts, dass eine Klasse als eine Art "Verkehrspolizist" fungiert und entscheidet, welche Unterklasse einer einzelnen Hierarchie instanziiert wird.

Im Factory-Pattern erstellen wir ein Objekt, ohne die Erstellungslogik offenzulegen. Eine Schnittstelle wird zum Erstellen eines Objekts verwendet, lässt jedoch die Unterklasse entscheiden, welche Klasse instanziiert werden soll. Anstatt jedes Objekt manuell zu definieren, können wir dies programmgesteuert tun.

Kurz gesagt, eine Factory ist ein Objekt, das Objekte ohne die Verwendung eines Constructors erstellen kann.

Abstract Factory Pattern

Wofür verwenden wir es?
Beispiel: wir bauen ein GUI-Framework mit Widgets wie Button, TextBox, Drop-Down-List und mit Themes (Material Design-Theme und Ant-Theme). Je nachdem welches Theme ausgewählt ist, sollen die Widgets in diesem Stil angezeigt werden. So wollen wir Material Design-Theme keinen Ant-Button sehen.

Das Abstract Factory Pattern stellt eine Schnittstelle zum Erstellen von Klassenfamilien ohne konkrete Implementierungen bereit.

Anderes Beispiel:
Stellen wir uns vor, wir erstellen ein Shopsystem für ein Möbelhaus. Der Laden verkauft Tische, Sofas, etc... Doch es gibt nicht nur einen Tischtyp, sondern mehrere Tischtypen. Diese Tischtypen könnte man auch eine Tischfamilie nennen, daher gibt es für jeden Tischtyp unterschiedliche Klassen. Allerdings muss er auch zu den Möbelstücken ähnlicher Stile zusammengebracht werden können (z.B. Landhaus-Stil).

Das abstract Factory Pattern verwenden wir, wenn unser Code mit verschiedenen Familien verwandter Produkte arbeiten muss, aber nicht von den konkreten Klassen dieser Produkte abhängig sein soll. Achtung: Der Code kann komplizierter werden, als er sein sollte, da mit diesem Muster viele neue Schnittstellen und Klassen eingeführt werden.

Builder Pattern

Wofür verwenden wir es?
Zum Beispiel wenn wir ein Dokument (Powerpoint-Slide) exportieren wollen in verschiedene Formate (PDF, Movie, Image...).

Das Builder-Muster wurde eingeführt, um einige der Probleme mit Factory- und Abstract Factory-Entwurfsmustern zu lösen (z.B. wenn das Objekt sehr viele Attribute enthält). Es wird meistens verwendet, wenn ein Objekt nicht in einem Schritt erstellt werden kann.

Das Builder Pattern konstruiert ein komplexes Objekt aus einfachen Objekten mithilfe eines Schritt-für-Schritt-Ansatz. Eine Builder-Klasse erstellt in dem Sinne schrittweise das endgültige Objekt. Dieser Builder ist unabhängig von anderen Objekten.