Das Facade Design Pattern
Studienprojekt von Philipp Hauer. 2009 - 2010. ©
Strategy, Observer, Decorator, Factory Method, Abstract Factory, Singleton, Command, Composite, Facade, State
Literaturverzeichnis, Philipps Blog
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, andere wollen sich Stempel herstellen lassen und wieder andere sind nur mit dem Frankieren und Versenden von Briefen beschäftigt.
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:
- Computer muss angeschaltet werden.
- Zugriff auf das Textverarbeitungsprogramm über den Computer.
- Das Textverarbeitungsprogramm muss geöffnet werden.
- Mit dem Textverarbeitungsprogramm muss das Dokument erstellt werden
- Drucker muss angeschaltet werden
- Drucker muss konfiguriert werden
- Papier muss in den Drucker gelegt werden
- Das Dokument muss gedruckt werden
- Der Drucker sollte ausgeschaltet werden
- Der Computer sollte ausgeschaltet werden
- Das Dokument muss mit dem Stift unterschrieben werden
- Das Dokument muss mit dem Stempel gestempelt werden
- Das Dokument muss mit Hilfe des Briefmarkenautomaten frankiert werden
- Das Dokument muss schließlich in den Briefkasten geworfen werden.
//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!
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.
}
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
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.
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.
- 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.