Strukturmuster

Adapter (Adapter)

*Zweck: Passe die Schnittstelle der Klasse an eine andere von den Klienten erwartete Schnittstelle an. Das Adaptermuster lässt Klassen zusammenarbeiten, die wegen inkompatibler Schnittstellen ansonsten nicht in der Lage wären.
*Struktur:
Klassen-
Adapter
Klassenadapter
Abb. 2.1.1 Ein Klassenadapter verwendet Mehrfachvererbung, um eine Schnittstelle an eine andere anzupassen

*Struktur:
Objekt-
Adapter
Objektadapter
Abb. 2.1.2 Ein Objektadapter verwendet Objektkomposition
*Teilnehmer:

  • Ziel
    • definiert die anwendungsspezifische vom Klienten verwendete Schnittstelle.
  • Klient
    • arbeitet mit Objekten, die der Zielschnittstelle entsprechen.
  • AdaptierteKlasse
    • definiert eine existierende und anzupassende Schnittstelle.
  • Adapter
    • passt die Schnittstelle der anzupassenden Klasse an die Zielschnittstelle an.

*Anwendbarkeit: Verwenden sie das Adaptermuster, wenn

  • Sie eine existierende Klasse verwenden wollen, deren Schnittstelle aber nicht der von Ihnen benötiten Schnittstelle entspricht.
  • Sie eine wiederverwendbare Klasse erstellen wollen, die mit unabhängigen oder nicht vorhersehbaren Klassen zusammenarbeitet, das heißt, Klassen, die nicht notwendigerweise kompatible Schnittstellen besitzen.
  • (nur für Objektadapter) Sie verschiedene existierende Unterklassen benutzen wollen, aber es unpraktisch ist, die Schnittstellen jeder einzelnen Unterklasse durch Ableiten anzupassen. Ein Objektadapter ist in der Lage, die Schnittstelle seiner Oberklasse anzupassen.


Brücke (Bridge)

*Zweck: Entkopple eine Abstraktion von ihrer Implementierung, so dass beide unabhängig voneinander varriert werden können.
*Struktur: Brücke
Abb. 2.2 Brücke
*Teilnehmer:

  • Abstraktion
    • definiert die Schnittstelle der Abstraktion.
    • verwaltet eine Referenz auf ein Objekt vom Typ Implementor.
  • VerfeinerteAbstraktion
    • erweitert die durch die Abstraktion definierte Schnittstelle.
  • Implementor
    • definiert die Schnittstelle für die Implementierungsklassen. Diese Schnittstelle muss nicht genau der Schnittstelle von Abstraktion entsprechen; beide Schnittstellen können sich durchaus erheblich voneinander unterscheiden. Typischerweise bietet die Schnittstelle von Implementor nur primitive Operationen, während Abstraktion komplizierte Operationen auf Basis dieser Primitiven definiert.
  • KonkreterImplementor
    • implementiert die Implementorschnittstelle und definiert seine konkreten Implementierungen.

*Anwendbarkeit: Verwenden sie das Brückenmuster, wenn

  • Sie eine dauerhafte Verbindung zwischen Abstraktion und ihrer Implementierung vermeiden wollen. Dies kann zum Beispiel der Fall sein, wenn die Implementierung zur Laufzeit ausgewähl werden können soll.
  • sowohl die Abstraktion als auch ihre Implementierungen durch Unterklassenbildung erweiterbar sein sollen. Das Brückenmuster ermöglicht es Ihnen in diesem Fall, verschiedene Abstraktionen und Implementierungen zu kombinieren und unabhängig voneinander zu erweitern.
  • Änderungen in der Implementierung einer Abstraktion keine Auswirkungen auf Klienten haben sollen; das heißt, ihr Code sollte nicht erneut übersetzt werden müssen.
  • (C++) Sie die Implementierung einer Abstraktion vollständig vor ihren Klienten verstecken wollen. In C++ ist die Repräsentation einer Klasse in der Klassenschnittstelle sichtbar.
  • Sie eine starke Vergrößerung der Anzahl ihrer Klassen zu verzeichnen haben. Solch eine Klassenhierachie ist ein Anzeichen dafür, dass man ein Objekt in zwei Teile aufspalten sollte. Rumbaugh verwendet die Bezeichnung "verschachtelte Generalisierungen" (nested generalizations), um auf solche Klassenhierachien zu verweisen.
  • Sie eine Implementierung von mehreren Objekten aus gemeinsam nutzen wollen, möglicherweise unter Verwendung von Referenzzählung (reference counting), und dieser Sachverhalt vor dem Klienten verborgen sein soll. Ein einfache Beispiel ist Copliens 1 Stringklasse, bei der mehrere Objekte dieselbe Stringrepräsentation gemeinsam nutzen können (die Klassen String und StringRep).


Dekorierer (Decorator)

*Zweck: Erweitere ein Objekt dynamisch um Zuständigkeiten. Dekorierer bilden eine flexible Alternative zur Unterklassenbildung, um die Funktionalität der Klasse zu erweitern.
*Struktur: Dekorierer
Abb. 2.3 Dekorierer
*Teilnehmer:
  • Komponente
  • KonkreteKomponente
    • definiert ein Objekt, das um zusätzliche Funktionalität erweitert werden kann.
  • Dekorierer
    • verwaltet eine Referenz auf ein Komponentenobjekt und definiert eine Schnittstelle, die der Schnittstelle von Komponente entspricht.
  • KonkreterDekorierer
    • fügt der Komponente Funktionalität hinzu.
*Anwendbarkeit: Verwenden sie das Dekorierermuster,

  • um einzelnen Objekten zusätzliche Funktionalität dynamisch und transparent hinzuzufügen, ohne andere Objekte mit einzubeziehen.
  • um Funktionalität hinzuzufügen, die auch wieder entfernt werden kann.
  • wenn die Erweiterung mittels Unterklassenbildung nicht praktisch durchführbar ist. Mitunter sind eine große Anzahl voneindanter unabhängiger Erweiterungen möglich, die zu einer Explosion der Unterklassenanzahl führen würde, wollte man jede Kombination unterstützen. Ein anderer Grund kann sein, dass eine Klassendefinition versteckt ist oder aus anderen Gründen zum Ableiten nicht verfügbar ist.


Fassade (Facade)

*Zweck: Biete eine einheitliche Schnittstelle zu einer Menge von Schnittstellen eines Subsystems. Die Fassadenklasse definiert eine abstrakte Schnittstelle, welche die Benutzung des Subsystems vereinfacht.
*Struktur: Fassade
Abb. 2.4 Fassade
*Teilnehmer:
  • Fassade
    • weiß, welche Subsystemklassen für eine Anfrage zuständig sind.
    • delegiert Anfragen von Klienten an das zuständige Subsystemobjekt.
  • Subsystemklassen
    • implementieren die Subsystemfunktionalität.
    • führen von der Fassade zugewiesene Aufgaben aus.
    • wissen nicht von der Fassade, das heißt, sie besitzen keine Referenz auf sie.
*Anwendbarkeit: Verwenden sie das Fassadenmuster, wenn

  • Sie eine einfache Schnittstelle zu einem komplexen Subsystem anbieten wollen. Subsysteme werden im Laufe ihrer Entwicklung oft komplexer. Die meisten Muster, wendet man sie an, führen zu kleineren Klassen. Dies macht das Subsystem leichter wiederverwendbar und einfacher anpassbar. Für Klienten, die es nicht anpassen wollen, wird es aber auch schwieriger, es zu verwenden. Eine Fassade kann eine einfache voreingestellte Sicht auf das Subsystem bieten, die den meisten Klienten genügt. Nur die Klienten, die ein größere Anpassbarkeit benötigen, müssen hinter die Fassade schauen.
  • es viele Abhängigkeiten zwischen den Klienten und den Implementierungsklassen einer Abstraktion gibt. Die Einführung einer Fassade entkoppelt die Subsysteme von Klienten und anderen Subsystemen, wobei die Unabhängigkeit und die Portabilität des Subsystems gefördert wird.
  • Sie ihre Subsysteme in Schichten aufteilen wollen. Man verwendet eine Fassade, um einen Eintrittspunkt zu jeder Subsystemschicht zu definieren. Wenn die Subsysteme voneinander abhängen, können sie die Anhängigkeit zwischen Ihnen vereinfachen, indem Sie sie ausschließlich über ihre Fassade miteinander komunizieren lassen.


Fliegengewicht (Flyweight)

*Zweck: Nutze Objekte kleinster Granularität gemeinsam, um große Mengen von ihnen effizient verwenden zu können.
*Klassen-
Struktur:
Fliegengewicht
Abb. 2.5.1 Klassenstruktur des Fliegengewichts
*Objekt-
Struktur:
Fliegengewicht
Abb. 2.5.2 Objektstruktur des Fliegengewichts
*Teilnehmer:
  • Fliegengewicht
    • deklariert eine Schnittstelle, durch die Fliegengewichte einen extrinsischen Zustand erhalten und verarbeiten können.
  • KonkretesFliegengewicht
    • implementiert die Fliegengewichtschnittstelle und erweitert das Objekt um einen intrinsischen Zustand, sofern vorhanden.
  • GetrenntGenutztesFliegengewicht
    • nicht alle Fliegengewichtklassen müssen gemeinsam genutzt werden. Die Fliegengewichtschnittstelle ermöglicht eine gemeinsame Nutzung, erzwingt sie aber nicht. Es ist üblich, dass ab einer bestimmten Hierarchieebene in der Objektstruktur Fliegengewichtobjekte, die nicht gemeinsam genutzt werden, über Exemplare von konkreten Fliegengewichten als Kindobjekte verfügen.
  • FliegengewichtFabrik
    • erzeugt und verwaltet die Fliegengewichtobjekte.
    • stellt sicher, dass Fliegengewichte auf korrekte Weise gemeinsam genutzt werden. Fragt ein Klient nach einem Fliegengewicht, so liefert die FliegengewichtFabrik ein existierendes Exemplar oder erzeugt anderenfalls ein neues.
  • Klient
    • verwaltet eine Referenz auf Fliegengewichte.
    • berechnet oder speichert den extrinsischen Zustand der Fliegengewichte.
*Anwendbarkeit: Der durch die Verwendung des Fliegengewichtsmusters erzielbare Gewinn hängt stark von den Randbedingungen seines Einsatzes ab. Wenden Sie das Fliegengewichtmuster nur an, wenn alle folgenden Bedingungen zutreffen:

  • Eine Anwendung verwendet eine große Menge von Objekten.
  • Die Speicherkosten sind allein aufgrund der Anzahl von Objekten groß.
  • Ein Großteil des Objektzustands kann in den Kontext verlagert und somit extrinsisch gemacht werden.
  • Viele Gruppen von Objekten können durch relativ wenige geimeinsam genutzte Objekte ersetzt werden, sobald einmal der extrinsische Zustand entfernt wurde.
  • Die Anwendung hängt nicht von der Identität der Objekte ab. Da Fliegengewichtobjekte gemeinsam genutzt werden, liefern Identitätstests bei konzeptuell unterschiedlichen Objekten ein positives Ergebnis zurück.


Kompositum (Composite)

*Zweck: Füge Objekte zu Baumstrukturen zusammen, um Teil-Ganzes-Hierarchien zu repräsentieren. Das Kompositionsmuster ermöglicht es Klienten, sowohl einzelne Objekte als auch Kompositionen von Objekten einheitlich zu behandeln.
*Struktur: Kompositum
Abb. 2.6 Kompositum
*Teilnehmer:
  • Komponente
    • deklariert die Schnittstelle für Objekte in der zusammengefügten Struktur.
    • implementiert, sofern angebracht, ein Defaultverhalten für die allen Klassen gemeinsame Schnittstelle.
    • deklariert eine Schnittstelle zum Zugriff auf und zur Verwaltung von Kindobjekten.
    • definiert optional eine Scnittstelle zum Zugriff auf das Elternobjekt einer Komponente innerhalv der rekursiven Struktur und implementiert sie, falls dies angebracht erscheint.
  • Blatt
    • repräsentiert Blattobjekte in der Komposition. Ein Blatt besitzt keine Kindobjekte.
    • definiert Verhalten für die primitiven Objekte in der Komposition.
  • Kompositum
    • definiert Verhalten für Komponenten, die Kindobjekte haben können.
    • speichert Kindobjektkomponenten.
    • implementiert kindobjekt-bezogene Operationen der Schnittstelle von Komponente.
  • Klient
    • manipuliert die Objekte in der Komposition durch die Schnittstelle von Komponente.
*Anwendbarkeit: Verwenden Sie das Kompositionsmuster, wenn

  • Sie Teil-Ganzes-Hierarchien von Objekten repräsentieren wollen.
  • Sie wollen, dass Klienten in der Lage sind, die Unterschiede zwischen zusammengesetzten und einzelnen Objekten zu ignorieren. Klienten behandeln alle Objekte in der zusammengesetzten Struktur einheitlich.


Proxy (Proxy)

*Zweck: Kontrolliere den Zugriff auf ein Objekt mit Hilfe eines vorgelagerten Stellvertreterobjekts.
*Struktur: Proxy
Abb. 2.7 Proxy
*Teilnehmer:
  • Proxy
    • verwaltet eine Referenz, die es dem Proxy ermöglicht, auf das eigentliche Subjekt zuzugreifen. Das Proxy kann die Schnittstelle des Subjekts verwenden, wenn sie mit der von EigentlichesSubjekt identisch ist.
    • bietet eine Schnittstelle, die mit der von Subjekt identisch ist, so dass ein Proxy für das eigentliche Subjekt eingesetzt werden kann.
    • kontrolliert den Zugriff auf das eigentliche Subjekt und ist potentiell dafür zuständig, es zu erzeugen und zu löschen.
    • weitere Zuständigkeiten hängen von der Art des Proxys ab:

      Remote-Proxies kodieren eine Anfrage und ihre Argumente und senden sie an das eigentliche Subjekt in einem anderen Adressraum.

      Virtuelle Proxies können zusätzliche Information über das eigentliche Subjekt zwischenspreichern, so dass sie den Zugriff verzögern.

      Schutzproxies überprüfen, dass der Aufrufer die zum Ausführen des Befehls notwendigen Zugriffsrechte besitzt.

  • Subjekt
    • definiert die gemeinsame Schnittstelle von EigentlichesSubjekt und Proxy, so dass ein Proxy überall dort benutzt werden kann, wo ein EigentlichesSubjekt erwartet wird.
  • EigentlichesSubjekt
    • definiert das eigentliche Objekt, das furch das Proxy repräsentiert wird.
*Anwendbarkeit: Das Proxymuster ist anwendbar, sobald es den Bedarf nach einer anpassungsfähigeren und intelligenteren Referenz auf ein Objekt als einen einfachen Zeiger gibt. Es folgen einige verbreitete Situationen, in denen das Proxymuster anwendbar ist:

  1. Ein Remote-Proxy stellt einen lokalen Stellvertreter für ein Objekt in einem anderen Adressraum dar.
  2. Ein virtuelles Proxy erzeugt teure Objekte auf Verlangen (siehe Buch).
  3. Ein Schutzproxy kontrolliert den Zugriff auf das Originalobjekt. Schutzproxies sind nützlich, wenn Objekte über unterschiedliche Zugriffsrechte verfügen sollen. Beispielsweise schützen Kernel-Proxies im Betriebssystem Choices den Zugriff auf Betriebssystemobjekte.
  4. Eine Smart-Reference ist ein Ersatz für einen einfachen Zeiger, der zusätzliche Aktionen ausführt, wenn auf das Objekt zugegriffen wird. Typische Verwendungen umfassen
    • das Zählen der Referenzen auf das eigentliche Objekt, so dass es automatisch freigegeben werden kann, wenn keine Referenzen mehr auf das Objekt existieren (auch Smart-Pointer genannt).
    • das Laden eines persistenten Objekts in den Speicher, wenn es das erste Mal dereferenziert wird.
    • das Testen, ob das eigentliche Objekt gelockt ist, bevor darauf zugegriffen wird. Dies dient zum Sicherstellen, dass kein anderes Objekt es ¨ndern kann.

Summarized by: Dipl.-Ing. (univ.) Dipl.-Ing. (FH) Ralf Isken
Only for private use.
Source: Entwurfsmuster; Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides; Addison-Wesley; 1996


Literaturhinweise

1 James O. Coplien. Advanced C++ Programming Styles and Idioms. Addison-Wesley, Reading, MA, 1992 (zurück)