Das Facade Design Pattern

Studienprojekt von Philipp Hauer. 2009 - 2010. ©

Inhalt

Einführung

Gegeben sei die Modellierung einer Firmenverwaltung. Die Verwaltung nutzt dabei eine Reihe von Klassen: Dokumente, Computer, Textverarbeitungsprogramme, Drucker, Stifte, Stempel, Briefkästen und Briefmarkenautomaten. Das Klassensystem wird von verschiedenen Benutzern (Clients) verwendet: Einer nutzt lediglich den Computer um Texte zu schreiben, andere benötigen nur den Stift und wieder andere sind nur mit dem Frankieren und Versenden von Briefen beschäftigt.

Facade Design Pattern Einführung

Die meisten Benutzer des Systems wollen jedoch ein Schreiben aufsetzen, drucken und gleich verschicken. Dazu sind alle Systemklassen und eine Reihe von immer gleichen Befehlen notwendig:

  1. Computer muss angeschaltet werden.
  2. Zugriff auf das Textverarbeitungsprogramm über den Computer.
  3. Das Textverarbeitungsprogramm muss geöffnet werden.
  4. Mit dem Textverarbeitungsprogramm muss das Dokument erstellt werden
  5. Drucker muss angeschaltet werden
  6. Drucker muss konfiguriert werden
  7. Papier muss in den Drucker gelegt werden
  8. Das Dokument muss gedruckt werden
  9. Der Drucker sollte ausgeschaltet werden
  10. Der Computer sollte ausgeschaltet werden
  11. Das Dokument muss mit dem Stift unterschrieben werden
  12. Das Dokument muss mit dem Stempel gestempelt werden
  13. Das Dokument muss mit Hilfe des Briefmarkenautomaten frankiert werden
  14. Das Dokument muss schließlich in den Briefkasten geworfen werden.
Die notwendigen Schritte ausprogrammiert:

//Gegeben: Alle benötigten Klassen sind korrekt instanziiert, initialisiert und bekannt
String text = "Dieser Text soll verschickt werden";

//Computer muss angeschaltet werden.
computer.an();
//Zugriff auf das Textverarbeitungsprogramm über den Computer.
Textverarbeitungsprog textverarbeitungsprog = computer.getTextverarbeitungsprog();
//Das Textverarbeitungsprogramm muss geöffnet werden.
textverarbeitungsprog.oeffnen();
//Mit dem Textverarbeitungsprogramm muss das Dokument erstellt werden
Dokument dokument = textverarbeitungsprog.getDokument(text);
//Drucker muss angeschaltet werden
drucker.an();
//Drucker muss konfiguriert werden
drucker.konfigurieren();
//Papier muss in den Drucker gelegt werden
drucker.papierNachfuellen();
//Das Dokument muss gedruckt werden
computer.drucke(dokument);
//Der Drucker sollte ausgeschaltet werden
drucker.aus();
//Der Computer sollte ausgeschaltet werden
computer.aus();
//Das Dokument muss mit dem Stift unterschrieben werden
stift.unterschreiben(dokument);
//Das Dokument muss mit dem Stempel gestempelt werden
stempel.stempel(dokument);
//Das Dokument muss mit Hilfe des Briefmarkenautomaten frankiert werden
briefmarkenautomat.briefmarkeBezahlen(dokument, 2);
//Das Dokument muss schließlich in den Briefkasten geworfen werden.
briefkasten.briefEinwerfen(dokument);			

Schauen wir uns diese Vorgehensweise genau an:

  • Systemwissennotwendig. Jeder Client muss nicht nur jede benötigte Klasse des Systems kennen, sondern auch ihr Zusammenspiel und ihre Funktionsweise, um sie nutzen zu können.
  • Abhängigkeiten und geringe Änderungsstabilität. Da jeder Client viele verschiedene Klassen kennen muss, steigen seine Abhängigkeiten. Er ist hart an das System gekoppelt. Änderungen am System (Dokumente sollen speziell eingepackt werden, Briefmarken werden teuerer, die Drucker-API ändert sich, ein neues Textverarbeitungsprogramm mit anderer API wird eingeführt, Macs werden statt PCs verwendet ;-) ) führen zwangläufig dazu, dass der Clientcode bricht oder nicht mehr korrekt funktioniert - und das gilt für jeden Client, der das System nutzt. Die Folge ist hoher Wartungsaufwand.
  • Coderedundanz und Gefahr von Inkonsistenz. Alle Clients, die ein Schreiben aufsetzen, drucken und verschicken wollen, müssen immer den gleichen Code schreiben. Dabei kann es sehr schnell passieren, dass Schritte ausgelassen werden oder die APIs falsch verwendet wird.

Wie wäre es, wenn wir eine Instanz zwischen System und Benutzern des System schalten? Diese Instanz stellt ein einheitliche und vereinfachte Schnittstelle (API) zum Schreiben, Drucken und Verschicken von Dokumenten bereit. Also, führen wir diese Instanz ein: der Sekretär - unsere Fassade!

Facade Design Pattern Einführung

Der Sekretär stellt eine vereinfachte Schnittstelle zur Nutzung des Systems bereit:

class Sekretaer {

    public void schreibenVersenden(String text){
        //Gegeben: Alle benötigten Klassen sind korrekt instanziiert, initialisiert und bekannt

        //Computer muss angeschaltet werden.
        computer.an();
        //Das Textverarbeitungsprogramm muss geöffnet werden.
        Textverarbeitungsprog textverarbeitungsprog = computer.getTextverarbeitungsprog();
        textverarbeitungsprog.oeffnen();
        //Mit dem Textverarbeitungsprogramm muss das Dokument erstellt werden
        Dokument dokument = textverarbeitungsprog.getDokument(text);
        //Drucker muss angeschaltet werden
        drucker.an();
        //Drucker muss konfiguriert werden
        drucker.konfigurieren();
        //Papier muss in den Drucker gelegt werden
        drucker.papierNachfuellen();
        //Das Dokument muss gedruckt werden
        computer.drucke(dokument);
        //Der Drucker sollte ausgeschaltet werden
        drucker.aus();
        //Der Computer sollte ausgeschaltet werden
        computer.aus();
        //Das Dokument muss mit dem Stift unterschrieben werden
        stift.unterschreiben(dokument);
        //Das Dokument muss mit dem Stempel gestempelt werden
        stempel.stempel(dokument);
        //Das Dokument muss mit Hilfe des Briefmarkenautomaten frankiert werden
        briefmarkenautomat.briefmarkeBezahlen(dokument, 2);
        //Das Dokument muss schließlich in den Briefkasten geworfen werden.
        briefkasten.briefEinwerfen(dokument);
    }
    
    //Instanzvariable der genutzten Objekte Computer, Drucker, Stift etc.
}			
Der Client kann die Funktionalität des Systems fortan bequem über den Sekretär nutzen:

Sekretaer sekretaer = new Sekretaer();
sekretaer.schreibenVersenden("Dieser Text soll verschickt werden");			

Vorteile:

  • Der Sekretär vereinfacht die Benutzung unseres Klassensystems durch den Client. Dieser muss fortan kein Wissen mehr um die notwendigen Klassen und Verarbeitungsschritte zum Aufsetzen und Verschicken eines Schreibens besitzen. Natürlich kann die Schnittstelle des Sekretärs beliebig um weitere Methoden zur einfachen Nutzung anderer Systemfunktionalitäten erweitert werden.
  • Clients, die nur ausgewählte Klassen unseres Systems nutzen wollen oder anspruchsvollere Aufgaben mit dem System erledigen möchten, für die die Schnittstelle des Sekretärs nicht ausreicht, können die Klassen weiterhin direkt nutzen. Natürlich kann dies auch verhindert werden. Siehe Variationen.
  • Wartbarkeit und Änderungsstabilität durch verringerte Abhängigkeiten und lose Kopplung zwischen Client und System. Der Client ist fortan nur noch von einer einzigen Klasse abhängig: dem Sekretär, der den Zugriff auf das System vereinheitlicht und vereinfacht. Änderungen an Klassen des Systems haben keinen direkten Einfluss mehr auf Code der Clients, solange diese auch ausschließlich den Sekretär nutzen. Änderungen an Systemklassen (andere Drucker-API oder Textverarbeitungs-API) schlagen höchstens bis in die Implementierung der Sekretärmethode schreibenAufsetzen() durch. Die Sekretär-API nach außen bleibt jedoch konstant. Siehe Vorteile.
  • Betrachten wir den Sekretär als Einstiegspunkt zu unserem System, so ist es auch denkbar, innerhalb des Systems weitere solcher Einstiegspunkte und Subsysteme zu definieren (beispielsweise eine Klasse Postfiliale mit einer vereinfachten Schnittstelle zum Frankieren und Absenden von Post mit den Systemklassen Briefmarkenautomat und Briefkasten). Dadurch realisieren wir einen Schichtenaufbau unseres Systems.
  • Keine Coderedundanz und Gefahr von Inkonsistenz beim Client. Da der Code zum Aufsetzen und Versenden von Dokumenten zentral bei dem Sekretär implementiert wurde, kann jeder Client diesen nutzen und muss ihn nicht jedes mal neu implementieren.

Nach dieser Einführung wird im folgenden Abschnitt das Facade Design Pattern (der Sekretär ist eine Facade) formalisiert, näher analysiert und diskutiert.

Das Sekretärbeispiel mit Facade Pattern Termini

Klasse Facade Teilnehmer
Sekretär Facade
System (Dokument, Computer, Drucker, Textverarbeitungsprogramm, Stift, Stempel, Briefmarkenautomat, Briefkasten) Subsystem

Analyse und Diskussion

Gang Of Four-Definition

Facade:
"Biete eine einheitliche Schnittstelle zu einer Menge von Schnittstellen eines Subsystems. Die Fassadenklasse definiert eine abstrakte Schnittstelle, welche die Verwendung des Subsystems vereinfacht."
([GoF], Seite 212)

Beschreibung

Facade Design Pattern

Das Facade Design Pattern definiert eine vereinfache Schnittstelle zur Benutzung eines Systems oder einer Menge von Objekten.

Gegeben sei ein komplexes Subsystem mit vielen Klassen und Abhängigkeiten zwischen ihnen. Clients die dieses Subsystem oder Teile davon nutzen möchten, müssen sich mit den verschiedenen Schnittstellen der enthaltenen Klassen befassen und die Funktionsweise verstehen. Dabei bauen sie zwangläufig viele Abhängigkeiten zu verschiedenen Objekten auf und koppeln sich eng an die Klassen des Subsystems.

Die Facade (dt. Fassade) wird zwischen Clients und dem Subsystem geschaltet. Es "kapselt" dabei das Subsystem, beinhaltet die komplexe Logik zum Arbeiten mit dem Subsystem und bietet für den Client eine vereinfachte Schnittstelle (Methoden) nach außen an. Die Fassade delegiert die Clientaufrufe ans Subsystem. Dadurch kann der Client das System über die Facade nutzen, ohne die Klassen, ihre Beziehungen, und Abhängigkeiten zu kennen.

Variationen

Schichten durch Fassaden

Betrachtet man das Facade Design Pattern als Mittel um ganze Systeme zu kapseln, so ist es auch möglich, diese Systeme selbst wiederum aus Teilsystemen aufzubauen, die über Fassaden angesprochen werden können.

Minimierung der Kopplung zwischen Client und Subsystem

Fassaden entkoppeln die Clients von den Subsystem, dahin gehend, dass Änderungen am Subsystem den Client nicht beeinflussen. Denkt man diesen Gedanken konsequent zu ende, so lässt sich die Entkopplung noch weiter steigern, in dem der Client nur noch eine abstrakte Schnittstelle der Fassade kennt und gegen diese arbeitet. Unterklassen dieser abstrakten Fassadenschnittstelle delegieren, dann die Aufrufe des Clients an ihr Subsystem. Jede Unterklasse kann dabei mit einer ganz anderen Subsystemimplementierung arbeiten oder das gleiche Subsystem anders verwenden. Der Client weiß nicht, welche Subsytemimplementierung er gerade verwendet.

Variation des Facade Entwurfsmusters: Abstrakte Facade/Fassade

Alternativ kann die Fassade auch mit den gewünschten Subsystemobjekten konfiguriert werden, damit der Client mit dem benötigten, angepassten Subsystem arbeitet.

Öffentliches oder privates Subsystem

Eine entscheidende Frage dreht sich um die Sichtbarkeit der Subsystemklassen für den Client.

Sollten die Subsystemklassen dem Client weiterhin (neben der vereinfachten Facadeschnittstelle) zur Verfügung stehen, so kann der Client diese Klassen jederzeit nutzen, sollte die durch die Fassade bereitgestellten Methoden einmal nicht ausreichen. Ein Beispiel hierfür sind Anwendungen erfahrener Entwickler, die über den normalen Gebrauch des Subsystems hinausgehen. Diese Möglichkeit wird dabei offengehalten.

Häufig ist eine direkte Verwendung des Subsystems jedoch nicht gewünscht. Client sollen sich eben nicht hart an Subsystemobjekte koppeln, da ihr Code dann bei Änderungen bricht. Er soll allein die wohldefinierte und konsistente Fassadenschnittstelle nutzen. Dann muss sichergestellt werden, dass der Client nur Zugriff auf die Fassade hat. In Java kann dies mit Packages und default-Sichtbarkeit realisiert werden.

Anwendungsfälle

Das Facade Design Pattern kann angewandt werden, wenn...

  • ... eine vereinfachte Schnittstelle zur Nutzung eines komplizierten Subsystems oder Menge von Objekten benötigt wird. Eine Facade bildet eine eingestellte Perspektive auf das Subsystem. Anspruchsvolle Clients können weiterhin das Subsystem direkt nutzen.
  • ... die Reduzierung der Abhängigkeiten zwischen dem Client und dem benutzten Subsystem angestrebt wird. Die Clients sind fortan nur noch von der Facade abhängig und damit vom Subsystem entkoppelt. Die Unabhängigkeit und Portabilität des Subsystems steigt.
  • ... ein System in Schichten unterteilt werden soll. Die Schichten kommunizieren nur noch über Fassaden, die den Zugang zum Subsystem darstellen. Somit wird das System unterteilt und die Abhängigkeiten zwischen den Teilsystemen gesenkt.

[GoF] (Seite 212f) beschreibt das System eines Compilers, der aus vielen Klassen mit Verantwortlichkeiten besteht (Scanner, Parser, CodeGenerator, BytecodeStream, Token, Symbol etc.). Zu diesem System wird eine Fassade "Compiler" mit der Methode compile() definiert. Diese einfache Methode kapselt die notwendigen Schritte und das Zusammenspiel zwischen Objekten der Systemklassen, um einen Text zu compilieren. Der Client nutzt das Compilersystem über diese Schnittstelle der Compilerfassade. Fortgeschrittene Client können trotzdem weiterhin ausgewählte Objekte des Systems nutzen.

Vorteile

  • Vereinfachte Schnittstelle. Der Client kann ein komplexes System einfacher verwenden, ohne die Klassen des Systems zu kennen und sich mit ihren mannigfaltigen Schnittstellen auseinander zu setzen. Eine Auseinandersetzung mit der Komplexität des Systems ist nicht mehr notwendig. Stattdessen kann eine einzige wohldefinierte Schnittstelle der Fassade genutzt werden.
  • Entkopplung des Client vom Subsystem. Da der Client nur noch gegen die Fassade arbeiten, ist er unabhängig von Änderungen im Subsystem. Wartungen und Modifikationen am Subsystem bedeuten nur noch Änderungen innerhalb des Systems und höchstens der Fassadenimplementierung. Die Schnittstelle der Fassade nach außen bleibt davon unbetroffen. Kein Code der Clients bricht.

Änderungen im Subsystem pflanzen sich nicht mehr unkontrolliert durch die gesamte Applikation fort, sondern höchstens bis zur Implementierung der Facade. Die Facadeschnittstelle bleibt konstant. Die durch das Facade Design Pattern gewonnene Änderungsstabilität und den gesenkten Änderungsaufwand sei in folgenden Abbildungen illustriert.

Facade Design Pattern Vorteile Änderungsstabilität

Facade Design Pattern Vorteile Änderungsstabilität

  • Eine Fassade verringerte die Abhängigkeiten des Clients, da die Anzahl der Objekte, die vom Client gehändelt werden müssen, signifikant gesenkt werden kann.
  • Jedoch können wenn gewünscht anspruchsvolle Clients weiterhin die Fassade übergehen und die Objekte des Subsystems direkt verwenden, sollte die bereitgestellte Schnittstelle der Fassade einmal nicht ausreichen.

Anwendung in der Java Standardbibliothek

javax.swing.JOptionPane

Die Swing-Convenience-Klasse JOptionPane ist eine Fassade für Dialogfenster. Mit ihren statischen Methoden (showMessageDialog(), showInputDialog() etc.) stellt sie dem Client eine einfache Schnittstelle zur Erstellung von Dialogfenstern zur Verfügung. Der Client muss dabei keine Kenntnis von den verwendeten Swingkomponenten (JDialog, JRootPane, Frame), Layoutmanagern (BorderLayout), Listener (Windowlistener, Actionlistener) und den Utilityklassen (SunToolkit, SwingUtilities, UIManager, Math) haben.

Facade Design Pattern JOptionPane

Kommentare

Bitte auswählen:*
Franck 2016-05-07 23:08:56
echt sehr gute Aufstellung. Erstmal vielen Dank. Allerdings sollte vllt von Bedeutung als Kommentar in Deinem UML-Diagramm hinzuzufügen, dass die Fassade "Sekretaerin" über jede Instanz jeder Subkomponente verfügt, ohne die er die Anfragen von Klienten nicht delegieren kann.
Ich musste den Quellcode gucken, um es zu verstehen. Außerdem stelle ich mir die Frage, wo in deiner Implementierung die Abstraktion ist? Wie wäre die Architektur, falls du "Sekretaerin" aufsetzt?
Peter 2015-11-17 21:14:38
Wirklich sehr sehr gut erklärt. Danke.
Christoph 2015-09-08 10:22:33
Sehr sehr gute Aufstellung und Illustration der wichtigstens Entwurfsmuster. Brauche das gerade für ne Klausur und kann mich hiermit wunderbar vorbereiten! Vielen Dank
Tapta 2013-12-22 17:40:25
Schöne Erklärung :)

Müsste bei vorteile-1.jpg der obere rechte Client nicht auch betroffen sein? Bzw. wurde dort eine Abhängigkeit vergessen einzuzeichnen?

Gruß
Philipp 2013-02-25 21:09:30
Hey Alex,
vielen Dank für deinen Hinweis. :-)
Alex 2013-02-25 10:21:54
Zweimal ConcreteFacadeA?
Es war wohl A und B gemeint.
Philipp 2012-10-11 13:57:55
Hallo Taras,
das stimmt. Vielen Dank für deinen Hinweis.

Philipp
Taras 2012-10-11 13:47:59
Hi Philipp,

wieder mal ein sehr hilfreicher Artikel.
Einen kleinen Fehler habe ich noch:

In der Sekretärin-Klasse wird im Klassendiagramm die Methode \"schreibenVersenden(...)\" genannt.
Im Code-Beispiel dazu dann \"schreibenAufsetzen(...)\"


Gruß
Philipp 2012-06-25 10:40:40
Hallo Marco,
vielen Dank für deinen Hinweis. Du hast natürlich Recht.

Philipp
Marco 2012-06-25 10:07:28
Echt super Seite!!!

Jedoch müsste in der \"schreibenAufsetzen(String pString)\"

die Zeile....
String text = \"Dieser Text soll verschickt werden\";

geändert werden...

String text = pString;

da sonst die lokale Variable \"pString\" nirgendwo verwendet wird.

Seite: 1 -