Design Patterns

Creational Patterns

Design Patterns um Objekt zu erzeugen

Factory Pattern

Normalerweise erzeugt man neue Objekte direkt über den Konstruktor.

Foo y=new Foo(1, 'Test', 42);

Beim Factory Pattern gibt es eine Creator Klasse, die eine Methode anbietet, welche die Erzeugung des Obejkts für einen übernimmt.

class Creator
{

 ...

 Foo getNewFooWithParameterSet1()
 {
  return new Foo(1, 'Bar', 42);
 }

 ...
}

Möchte man jetzt ein neues Foo Objekt haben, ruft man einfach diese Methode auf.

Creator c=new Creator();
Foo y=c.getNewFooWithParameterSet1();

Creator kann auch ein Interface sein das von mehreren Klassen implementiert wird.

Vorteile

  • Factorymethoden können beschreibenden Namen haben (Konstruktoren müssen alle wie Klasse heißen).
  • Der (möglicherweise komplexe) Aufruf des Konstruktors kann gekapselt werden.

Abstract Factory

Man hat ein Factor Interface in dem verschiedene Methoden zur Erzeugung von Objekten definiert sind. Dem Client stehen jetzt mehrer konkrete Implementierung des Factory Interfaces zur Verfügung.

Builder Pattern

Man möchte ein Objekt Foo erzeugen, welches sehr komplex ist. Daher lagert man die Erzeugung in die Klasse Concretebuilder aus (erbt von abstrakter Klasse Builder). Der Client erzeugt jetzt ein Concretebuilder Objekt und gibt es einem Director Objekt dass für alle Objekte vom Typ Builder die Erzeugung anstoßen und zurückliefern kann.

Ähnlich wie Factory, die hat keinen Director sondern der Client stösst die Erzeugung in Concretebuilder Klasse selbst an. Das Factory Pattern verbirgt daher wie genau ein Objekt erzeugt werden muss, das Builder Pattern verbirgt die Logik zur Anstossung der Erzeugung.

Hier eine Variante eines Builders in Java. Erzeugt werden soll eine Klasse Foo. Die Klasse hat eine innere Klasse namens Builder. Diese muss man zuerst anlegen. Die Argumente des Konstruktors der Builder Klasse sind obligatorisch. Zusätzlich bietet sie für optionale Parameter noch Methoden an, die genau diesen Parameter setzen und die aktuelle Instanz des Builders zurückliefern. Das erlaubt es die Aufrufe zu verketten. Die Klasse Foo hat selbst nur einen Konstruktor, der ein Builder Objekt erwartet. Aufgerufen wird es von einer build() Methode des Builders mit sich selbst als Parameter

/**
 *
 * This is a class which should be constructed by a builder:
 * - Objects of this class are never in an unfinished status
 * - Class does not need to provide methods to modify it
 */

public class Foo
{
    private int      param1;
    private String   param2;  
    private Long     param3;
    private String   param4;

    /**
     * Constructor which reads everything from the Builder
     * @param builder
     */

    public Foo(Builder builder)
    {
        this.param1=builder.param1;
        this.param2=builder.param2;
        this.param3=builder.param3;
        this.param4=builder.param4;
    }

    /**
     * Builder object
     */

    public static class Builder
    {
        private int      param1;
        private String   param2;      
        private Long     param3;
        private String   param4;

        /**
         * Required Parameters have to be passed to the constructor of the Builder
         * @param pRequiredParm1
         * @param pRequiredParm2
         */

        public Builder(int pRequiredParm1, String pRequiredParm2)
        {
            this.param1=pRequiredParm1;
            this.param2=pRequiredParm2;
        }

        /**
         * Optional parameters
         * @param pOptionalParm3
         * @return
         */

        public Builder param3(Long pOptionalParm3)
        {
            this.param3=pOptionalParm3;
            return this;
        }

        /**
         * Optional Parameters
         * @param pOptionalParm4
         * @return
         */

        public Builder param4(String pOptionalParm4)
        {
            this.param4=pOptionalParm4;
            return this;
        }

        /**
         * Build an object of type Foo
         * @return
         */

        public Foo build()
        {
            return new Foo(this);
        }

    }
}

So kann man dann elegant Foo Objekte erzeugen. Erst die obligatorischen Parameter für den Builder, die optionalen verkettet aufgerufen und am Ende die build() Methode

Foo foo=new Foo.Builder(42, "Foo").param3(100L).param4("Bar").build();

Prototype

Weil es so aufwendig ist ein neues Objekt zu erzeugen nimmt man ein Vorhandenes (den Prototype) und clont es.

Singelton

Von einer Singelton Klasse gibt es genau ein oder kein Objekt. Es gibt eine Methode die dieses Objekt der Klasse initial erzeugt und ab dann zurückliefert.

Multion

Wie Singleton nur dass ein Pool von Objekten vorgehalten wird

Structural Design Patterns

Adapter Pattern

Client erwartet Interface A, wir haben aber nur Interface B. Also schreiben wir einen Adapter der die Schnittstellen übersetzt.

Es gibt zwei Varianten dieses Patterns:

Objektadapter

Das Adapter Objekt hat das Objekt mit der Schnittstelle B in sich gespeichert und bietet selbst Schnittstelle A an. Alle Anfragen an diese Schnittstelle leitet er dann an das Objekt in sich weiter und "übersetzt" es dabei auf die Schnittstelle B.

Class Adapter

Die Adapter Klasse erbt vom Objekt mit der Schnittstelle B und bietet selbst die Schnittstelle A an. Aufrufe an diese Schnittstelle werden an die Schnittstelle des Vaterobjektes weitergeleitet.

Bridge

Man hat zwei verschiedene Hierarchien. Eine bezieht sich auf das abstrakte Interface und eine auf das konkrete Interface verschiedener Implementierungen. Zum Beispiel könnte man abstrakt definieren, was einen Container ausmacht

Container Interface
|      |
List  Set

und parallel, was verschiedene Implementierungen ausmachen:

Implementierer Inferface
|                  |
Array basiert   Basiert auf verlinkten Objekten

Normalerweise würde man beide Hierarchien in eine mischen. Allerdings wird damit der Baum komplizierter, weil es für jede Kombination einen Repräsentanten geben muss. Zusätzlich hat eine Änderung an einer der beiden Hierarchien unnötigerweise Auswirkungen auf alle Repräsentanten. Daher lässt man beide Hierarchien getrennt, das Container Interface sieht vor, dass alle Implementierer dieses Interfaces eine Referenz auf einen Implementierer der anderen Hierarchie beinhalten.

Einen konkreten Repräsentanten erhält man, indem man sich ein Objekt erzeugt, welches eines der abstrakten Interfaces implementiert und eines, welches eine konkretes Implementierungsinterface implementiert. Das erste Objekt referenziert nun das zweite Objekt.

Composite

Man hat Objekte die sich ähnlich sind aber in kleinen Details unterscheiden, z.B. ein Dateisystem mit Dateien, Verzeichnissen, Links, usw.

COMPONENT
| |
| LEAF
COMPOSITE
COMPONENTabstrakte Schnittstelle, die auf alle Objekt zutrifft (im Dateisystembeispiel z.B. das Erzeugungsdatum oder die Größe)
LEAFimplementiert Schnittstelle und ist spezialisiert auf eines der Objekte (z.B. eine Datei im Dateisystembeispiel)
COMPOSITEimplementiert auch die Schnittstelle, enthält aber mehrere Objekt, die die Schnittstelle implementieren, z.B. ein Verzeichnis mit allen Dateien darin

Vorteile

  • Man kann Primitive und Kompositionen gleich behandeln

Decorator

Man hat ein Objekt, z.B. Fenster in einer GUI. Dieses soll erweitert werden ohne das Objekt selbst zu verändern. Normalerweise würde man dann von diesem Objekt erben. Angenommen es soll aber sehr viele verschiedene denkbare Kombinationen von Veränderungen geben, z.B. Fenster mit Scrollbalken, mit Menüleiste, mit Scrollbalken und Menüleiste, usw., gleichzeit werden aber nicht alle denkbaren Kombinationen immer gebraucht, würde das zu unnätig vielen verschiedenen Klassen führen.

Dazu nimmt man erst mal eine abstrakte Klasse COMPONENT, diese beinhaltet die Schnittstelle des zugrunde liegenden Objekts (hier eines Fensters). Zusätzlich gibt es eine Klasse CONCRETECOMPONENT. Diese implementiert die Schnittstelle (und ist hier ein einfaches Fenster).

Die Schnittstelle wird außerdem von der Klasse DECORATOR implementiert, die einen Konstruktor beinhaltet um ein Objekt der Klasse DECORATOR aufzunehmen. Dieses Objekt speichert die Klasse DECORATOR dann in sich.

Von der DECORATOR Klasse erben CONCRETEDECORATOR Klassen die die eigentlichen Erweiterungen für unser Fenster beinhalten.

COMPONENT
|  |
|  CONCRETE_COMPONENT
|
DECORATOR
|  |
|  CONCRETE_DECORATOR1
|
CONCRETE_DECORATOR2

Wenn man jetzt ein Fenster mit bestimmten Erweiterungen benötigt sieht der Aufruf so aus:

COMPONENT x=new CONCRETEDECORATOR1(new CONCRETEDECORATOR2(...(new CONRETECOMPONENT())));

Damit haben wir ein CONCRETECOMPONENT Objekt mit den Erweiterungen 1,2, ...

Facade

Wir haben eine oder mehrere unschöne und komplizierte Schnittstellen. Façade versteckt diese Schnittstelle hinter einer schönen und einfachen Schnittstelle. Wirkt für den Client ähnlich wie das Adapter Pattern, hier liegt der Fokus aber mehr auf der Vereinfachung der Schnittstelle für den Client.

Flyweigth

Man hat z.B. eine Textverarbeitung. Wenn man jetzt jeden eingegeben Buchstaben als Objekt abspeichern möchte, wäre es zu teuer in jedem Buchstaben alle Formatierungsanweisungen für diesen Buchstaben zu speichern. Daher macht man die Buchstabenobjekte zu Leichtgewichten (Flyweigt) in dem man die Formatierungsinformationen auslagert und alle Buchstaben mit gleicher Formatierung zeigen auf ein gemeinsames Formatierungsinformationsobjekt.

Proxy

Eine Klasse bietet Schnittstellen für etwas anderes an, z.B. für eine Netzwerkverbindung, oder für den Zugriff auf ein anderes Objekt. Typische Anwendung sind der Zugriff auf Resourcen die nicht, oder nur aufwendig dupliziert werden könnten. Das Proxyobjekt leitet alle Anfragen auf diese Resource dann weiter.

Interessantes Anwendungsbeispiel: Zugriffe auf Pointer werden über ein Proxyobjekt abgewickelt welches die Anzahlt der Referenzen auf den Pointer zählt.

Remote Proxy

Zugriffe auf Objekte die irgendwo "entfernt" liegen.

Virtual Proxy

Bietet Zugriffe auf komplexe Objekte an, die erst erzeugt werden wenn der Client sie wirklich braucht (lazy evaluation).

Copy on write Proxy

Ein Client fordert einen Klon an, dieser wird über den Proxy verfügbar gemacht und erst geclont wenn der Client ihn wirklich schreibend benutzt.

Cache Proxy

Der Proxy bietet den Zugriff auf Methoden an, die Rückgabewerte dieser Methoden werden im Proxy zwischgengespeichert und zurückgeliefert falls die gleiche Anfrage nochmal gestellt wird.

Protection Proxy

Für verschieden Clients werden unterschiedliche Zugriffsmöglichkeiten angeboten.

Firewall Proxy

Zu Zugriff auf bestimmte Objekte / Methoden wird durch den Proxy gegenüber "schlechten" Clients geschützt.

Synchronisation Proxy

Bietet synchronisierte Zugriffe auf Methoden an.

Smart Reference Proxy

Der Proxy führt eine Aktion aus wannimmer ein Objekt (de-) referenziert wird, z.B. zählen.

Behavioral Design Patterns

Chain of responsibility

Es gibt ein Interface Inter mit einer Methode setnext(Inter other) mit der man einem Objekt, das dieses Interface implementiert, ein weiteres Objekt zuordnen kann, das dieses Interface ebenfalls implementiert.

So kann man eine Kette aus dieses Objekten basteln.

Jetzt werden eine Reihe von diesen Objekten verbunden:

Foo x=new A().setnext( (new B()).setnext(new C()));

Jetzt kann ein Client eine Methode auf einem der Objekte aufrufen. Diese führen dann entweder selbst etwas aus, oder geben es dem nächsten Objekt in der Kette zur Ausführung.

Command Pattern

Jedes Objekt representiert eine Aktion (z.B. Drucken). Normalerweise würde man dafür nur eine Methode schreiben. Es hat aber Vorteile das in eine eigene Klasse auszulagern:

  • Man kann sich in den Objekten Informationen zur Aktion merken (z.B. Auftraggeber des Druckauftrages)
  • Man kann die Objekte die Aktionen repräsentieren sammeln und sie später ausführen und sie vorher z.B. noch umsortieren (z.B. nach Priorität des Druckauftrages)
  • Wenn man die Objekte nach der Verarbeitung aufhebt kann man leicht ein UNDO impelmentieren
  • Transaktionssicherheit lässt sich leichter implementieren (weil die Aktionen einen Status haben können)

Alle Command Klassen implementieren sinnvollerweise ein gemeinsames Interface.

Interpreter Pattern

Für ein gegeben Teilaufgabe will man eine andere Programmiersprache benutzen weil sie für dieses Aufgabe besser geeignet ist als die eigentlich benutze, beispielsweise SQL statt Java.

Iterator Pattern (Cursor Pattern)

Man hat einen Container und man läuft über die Elemente des Containers ohne verstehen zu müssen, wie der Container aufgebaut ist.

Typische Methoden:

next();
previous();

Mediator Pattern

Man hat viele Klassen mit vielen Schnittstellen. Diese Klassen sollen untereinandern kommunizieren. Damit die Klassen nicht so eng aneinander gebunden sind, sollen sie sich aber nicht direkt kommunizieren, sondern über ein Mediator Objekt, welches dafür eine saubere Schnittstelle anbietet.

Memento Pattern

Man hat ein Objekt das von außen verändert wird (über dessen Methoden). Jetzt will man die Möglichkeit haben ein UNDO zu implementieren. Das löst man so, dass das Objekt eine Kopie mit dem Ist-Zustand liefern kann. Diese kann sich der Aufrufer merken und bei Bedarf dem Objekt zurückgeben um den alten Zustand wiederherzustellen.

Originator Ein Objekt, das vom Caretaker verändert werden soll. Caretaker Ein Objekt, das den Originator verändern will. Memento Ein Objekt, dass vom Originator vor der Änderung an den Caretaker gegeben wird, damit er nach seiner Änderung den Originalzustand bei Bedarf wiederherstellen kann.

Observer Pattern

Immer wenn ein Event passiert, möchte man alle betroffenen Objekte informieren. Dazu hat man eine eine abstrakte Observer Klasse mit den Methoden notify() und update(). Diese Observer werden informiert. Und man hat Subjekt Klassen. Diese Klassen informieren. Wenn ein Subjekt von einem konkreten Observer update aufruft holt sich der Observer aktuelle Infos über das Subjekt. Immer wenn ein Event passiert, wird die Methode notfiy() des Observer Objekts aufgerufen.

Die Subjekt Objekte haben folgende Methoden: attach(Observer o); fügt einen Observer zum Subjekt hinzu detach(Observer o); entfernt einen Observer vom Subjekt notify(); Ruft notify() von allen gespeicherten Observern im Subjekt auf

     Observer
     |      |
ObserverA   ObserverB
      Subjekt
      |     |
SubjektA   SubjektB

State Pattern

Man hat ein Objekt, das verschiedene Zustände annehmen können soll. Zum Beispiel ein Objekt zum Zeichnen. Dieses Objekt soll dann mal Kreise mal Rechtecke mal Linien usw. malen. Jetzt könnte man eine Interface für das Tool erstellen und verschiedene Klassen implementieren dieses. Je nachdem welches Tool man benötigt erzeugt man dann ein Objekt dieser Klasse. Dann hat man aber bei jedem Toolwechsel ein neues Objekt, welches sich z.B. keine Zustände merken kann.

Beim State Pattern geht man daher anders vor. Es gibt ein Interface State. In diesem sind die Methoden definiert, deren Inhalt sich von Zustand zu Zustand unterscheidet. Hier z.B. paint(). Es gibt verschiedene Zustandsobjekte State1, State2, usw. die diese Methoden dann unterschiedlich implementieren. Zusätzlich gibt es dann noch das ein Context Objekt. Dieses hat eine Referenz auf eine State Objekt. Wenn das Context Objekt seine Zustand wechseln soll, wechselt es einfach sein State Objekt gegen ein anderes aus. Damit kann sich das Context Objekt Informationen merken, während es seine Zustand ändert.

Context
 State currentState;


     State
     | |
StateA |
       |
    State B

Strategy Pattern (Policy Pattern)

Man hat verschiedene Algorithmen für ein Problem und will zwischen ihnen hin zur Laufzeit und her schalten können. Dazu definiert man ein Strategy Interface und schreibt mehrere Strategien, die dieses Interface implementieren.

Context
  Strategy myStrategy;

        Strategy
        |     |
     StrategyA  StrategyB

Jetzt kann es natürlich vorkommen dass zwei Algorithmen (Strategien) gemeinsame Codeanteile haben. Das würde man normalerweise durch Vererbungshierarchie in den Strategien implementieren. Eine interessante Alternative kann es hier sein, das Strategy Pattern erneut anzuwenden.

Unterschied State und Strategy Pattern

Beim Strategy Pattern hat man verschiedene Strategien, die aber das gleiche tun. Zum Beispiel verschiedene Sortieralgorithmen. Alle erzeugen eine sortiere Liste, aber mit jeweils einer anderen Strategy. Beim State Pattern hingegen wird in den verschiedenen Zuständen völlig unterschiedliches gemacht. Zum Beispiel einmal ein Kreis gemalt und einmal ein Rechteck.

Template Methode Pattern

(Das hat nichts mit C++ Templates zu tun)

Es geht eine bestimmte Aufgabe mit einer Methode zu erledigen. Diese Aufgabe lässt sich in mehrere Teilschritte zerlegen. Jetzt gibt es verschiedenen Abwandlungen der Aufgabe, die sich in einigen der Teilschritte unterscheiden. Dazu entwirft man jetzt eine abstrakte Klasse. Diese Klasse beinhaltet die Template Methode. In der Methode wird für jeden Teilschritt der Aufgabe eine andere Methode aufgerufen. Diejenigen Teilschritte, die für alle Aufgaben gleich sind, können direkt in der abstrakten Klasse implementiert werden. Alle anderen bleiben abstrakt. Jetzt kann man von dieser abstrakten Klasse erben und einige der Teilschritte implementieren. Von dieser Klasse können wieder andere Klassen erben usw. So erhält man eine Klassenhierarchie, deren Elemente spezialisiert sind auf verschiedene Varianten der eigentliche Aufgabe. Im Prinzip ist das ganz normales Erben in objektorientierten Programmiersprachen. Der Trick ist, dass die Template Methode in der obersten abstrakten Klasse bereits alle Methoden aufruft, obwohl ein Großteil erst in den Erben implementiert werden.

Visitor Pattern

Man hat eine Klassenhierarchie, z.B. mit Autos.

      Element
       |   |
ElementA  ElementB

Für jedes Klasse sollen verschiedene Methoden bereitgestellt werden, deren Implementierung sich je nach Klasse unterscheidet (z.B. die Berechnung des aktuellen Marktwertes). Jetzt könnte man allen Klassen eine entsprechende Methode hinzufügen. Das hat aber den Nachteil, dass man alle Klassen entsprechend verändern muss, möglicherweise möchte man den Code an einer zentralen Stelle vorhalten. Eine solche Methode

doSomething(Element e);

hat aber das Problem, dass sie ermitteln müsste welches Element genau übergeben wurde, um zu entscheiden, welcher Code ausgeführt werden soll.

Hier bedient man sich eines Tricks. Man erzeugt eine Visitor Klasse. Diese hat für jede Element Klasse eine visit Methode

Visitor:
 visit(ElementA e) { ... }
 visit(ElementB e) { ... }
 ...

Jetzt haben wir schon mal für die unterschiedlichen Element Klassen unterschiedliche Methoden. Diese müssen nur noch richtig aufgerufen werden. Haben wir ein Visitor Objekt v und ein Element Objekt e führt v.visit(e) aber noch nicht zum Ziel, da e vom abstrakten Typ Element ist. Wir wissen nicht, welches Element e tatsächlich ist.

Deshalb erhalten die Element Klassen jetzt eine Methode accept Element:

 accept(Visitor v) { v.visit(this); }

Jetzt wird es einfach. Wenn man ein Visitor Objekt v hat und ein Element Objekt e, muss man nur noch e.accept(v) aufrufen, welches dann v.visit(this) aufruft. this ist dann nicht mehr vom abstrakten Typ Element sondern, vom tatsächlichen Element Typ. Diesen Trick nennt man auch Double Dispatch.

Das Ganze kann jetzt noch so erweitert werden, dass es nicht nur eine Visitorklasse gibt, sondern eine Hierarchie von Visitor Klassen (die verschiedene Aufgaben wahrnehmen).

      Visitor
      |    |
Visitor1  Visitor2

Durch die Auswahl des gewünschten Visitors legen man dann die ausgeführte Aufgabe fest. Neue Aufgaben können so hinzugefügt werden, ohne die Element Klassen zu verändern. Normalerweise müsste für eine neue Aufgabe potentiell jede Element Klasse verändert werden!