Entwurfsmuster in der Softwareentwicklung: Das etwas andere IKEA-Regalsystem

Seite 3: Unauffällige Kommunikation

Inhaltsverzeichnis

Das Strukturmuster Proxy bietet sich für die Steuerung der Zugriffskontrolle oder für das verzögerte Laden von Bildern, Videos und Audiodateien an. Die Steuerung von Zugriffskontrollen ist ein klassisches Problem in der Programmierung. Ein Beispiel veranschaulicht das: In einer Datenbank existieren Daten, die nur bestimmte Nutzer sehen dürfen. Eine Zugriffskontrolle ist nur dann möglich, wenn Klassen nicht direkt auf die Datenbank zugreifen können. Auch bei der Kommunikation zwischen zwei Hardwarekomponenten bietet sich die Verwendung eines Proxys an. Zum Beispiel ist typischerweise die Anzahl der maximal übertragbaren Daten pro Zeiteinheit begrenzt und man muss sie vorher einer Plausibilitätsprüfung unterziehen.

Zwischen dem Client und dem Service (einer Datenbank oder der Hardware) führt man einen Proxy mit derselben Schnittstelle ein. Der Client stellt die benötigte Schnittstelle als Interface bereit. Der Proxy implementiert sie anstelle des eigentlichen Objekts. Er kommuniziert mit dem Service und führt davor und/oder danach weitere Operationen aus, ohne dass der Client etwas von der Zwischenschicht merkt (Abbildung 5).

Der Proxy implementiert das Interface und führt Operationen aus (Abb. 5).

Die Vorteile dieser Methode sind klar ersichtlich: Zum einen kann man den Lebenszyklus des Service-Objekts vom Proxy zentral steuern. Der jeweilige Client braucht sich darum nicht mehr zu kümmern. Besonders bei aufwendigen Operationen wie beim Aufbau einer Datenbankverbindung kann er Laufzeit einsparen. Weiterhin funktioniert der Proxy, auch wenn der Service noch nicht zur Verfügung steht und kann dementsprechend reagieren. Ein Proxy-Objekt ist austauschbar, ohne dass man dafür den Client oder Service ändern muss – dies entspricht ebenfalls dem Open/Closed-Prinzip.

Nachteilig ist, dass dieses Strukturmuster neue Klassen benötigt. Außerdem muss der Proxy die Daten mit einer gewissen Verzögerung zurückgeben können, asynchrone Programmierung ist also empfehlenswert.

Der letzte Typ der aufgezählten Entwurfsmuster sind Verhaltensmuster. Sie beschäftigen sich vorrangig mit der Modellierung von komplexem Verhalten der Software. Hierzu gehört die Zuteilung von Zuständigkeiten an Objekte, aber auch Algorithmen, um die Flexibilität einer Software hinsichtlich ihres Verhaltens zu erhöhen. Sie zu verstehen, stellt für Anfänger oftmals eine Hürde dar.

Möchten Objekte von der Zustandsänderung eines anderen Objekts erfahren, bietet sich das Verhaltensmuster Observer an. Um es besser zu verstehen, hier ein Beispiel: Es existieren die Objekte Kunde und Laden. Der Kunde möchte gerne als Erster ein neues Smartphone kaufen. Deshalb fährt er mehrfach am Tag zu einem bestimmten Laden und überprüft, ob das Gerät vorrätig ist. Da sich das neue Smartphone jedoch noch auf dem Lieferweg befindet, bleiben die Besuche ohne Erfolg. Würde der Laden eine E-Mail an alle Kunden schicken und sie darüber informieren, dass das Gerät verfügbar ist, müsste der Kunde nicht mehr so oft das Geschäft betreten. Andererseits könnten sich nicht interessierte Kunden über diese Benachrichtigung ärgern und sie als Spam einstufen

Dieser Konflikt lässt sich durch das Verhaltensmuster Observer lösen. In diesem existieren im Wesentlichen die Akteure Publisher und Subscriber. Der Publisher (der Laden) stellt Informationen zur Verfügung. Der Subscriber (der Kunde) möchte hingegen die Informationen erhalten. Der Subscriber registriert sich beim Publisher, der ihn bei definierten Ereignissen benachrichtigt (Abbildung 6).

Der Publisher schickt maßgeschneiderte Informationen an den Subscriber (Abb. 6).

Der Publisher besitzt neben seiner eigentlichen Logik verschiedene Methoden, mit denen sich Subscriber an- und abmelden. Er kann sie auch benachrichtigen. Dabei meldet sich die Subscriber-Klasse nicht direkt, sondern über ein Interface an. Das Interface definiert die Nachrichtenschnittstelle. Meist besteht sie aus einer einzigen Update-Methode mit einem Kontext-Objekt. Das Kontext-Objekt beinhaltet zum Beispiel den Eventnamen oder weitere Eventdetails. Der konkrete Subscriber implementiert das Interface. Dadurch kann er sich benachrichtigen lassen. Für die Verknüpfung beider Elemente ist eine zusätzliche Client-Klasse notwendig. Diese erzeugt sowohl die Publisher- als auch die Subscriber-Klasse und registriert den Subscriber beim Publisher.

Durch dieses Verhaltensmuster lassen sich Beziehungen zwischen Objekten zur Laufzeit abbilden. Es führt zu hoher Wiederverwendbarkeit, Modularität und Flexibilität. Klassische Einsatzgebiete sind beispielsweise die Benachrichtigung bei Netzwerkverlust oder die Durchführung einer asynchronen Kommunikation zwischen verschiedenen Objekten. Die Benachrichtigung folgt keiner Reihenfolge, sondern läuft zufällig ab. Es gibt jedoch auch Nachteile: Meldet sich der Nutzer beim Publisher nicht ab oder meldet er sich mehrfach an, kann dies bei der Software zu nicht definierten Zuständen führen. Solche Fehler sind meist nur schwer zu finden. Besonders bei großen Projekten kann die Änderung eines Objekts zu einem Domino-Effekt führen, wenn ein Subscriber gleichzeitig wiederum ein Publisher ist. Auch ein sich immer wiederholender Aufruf ist so nicht ausgeschlossen.

Wenn man in verschiedene Klassen eine ähnliche Funktion implementieren möchte, die sich nur im Detail unterscheidet, eignet sich das Verhaltensmuster Template-Method. Es kann eine Art Skelett eines Algorithmus in der Basisklasse definieren, das man in den Unterklassen in bestimmten Schritten überschreiben kann.

Um die Funktionsweise zu erklären, bietet sich ein Beispiel an: Angenommen, es existiert ein Rechnungsanalyseprogramm, das automatisiert DOC-Dateien analysiert und notwendige Daten extrahiert. Anschließend gibt es die Daten in einem Modul zurück. Nun aktualisiert jemand das Programm, damit es dieselben Funktionen auch für PDF- und CSV-Dateien bietet. Jede dieser analysierten und extrahierten Daten setzt das Programm in einer eigenen Klasse um. Hierbei fällt die große Ähnlichkeit der drei Klassen auf. Nur die Art der Extraktion der Daten unterscheidet sich, während alle anderen Operationen identisch sind.

Durch die große Ähnlichkeit der drei Klassen bietet sich das Template Method-Pattern an. Das Verhaltensmuster Template-Method vermeidet schlecht wartbaren doppelten Code (Abbildung 7). Zuerst teilt man den Algorithmus in verschiedene Schritte auf. Ein Schritt ist in diesem Beispiel die Datenanalyse, ein anderer die Datenextraktion. Beide entsprechen jeweils Methoden in der abstrakten InvoiceMiner-Klasse. Eine Entwicklerin beziehungsweise ein Entwickler implementiert dann die Methode Datenanalyse processData(data) im InvoiceMiner. Die abstrakte Methode Datenextraktion extractData() lässt sich in der jeweiligen konkreten Klasse implementieren, wie PDFInvoiceMiner.

Somit lässt sich mit dem Verhaltensmuster Template-Method gemeinsamer Code zwischen verschiedenen Klassen mit demselben Use Case teilen. Nachteilig ist die schwierigere Wartbarkeit bei vielen einzelnen Methoden. Das vorgegebene Grundgerüst beschränkt aber nicht die Funktion einer Klasse.

Der Publisher schickt maßgeschneiderte Informationen an den Subscriber (Abb. 7).