Wollen Sie eine bereits be­stehen­de Klasse in einer ob­jekt­ori­en­tier­ten Software um neue Funk­tio­na­li­tä­ten erweitern, haben Sie zwei ver­schie­de­ne Mög­lich­kei­ten: Die einfache, aber auch schnell un­über­sicht­li­che Lösung ist, Un­ter­klas­sen zu im­ple­men­tie­ren, die die Ba­sis­klas­se in ent­spre­chen­der Weise ergänzen. Als Al­ter­na­ti­ve kann man eine De­ko­rie­rer-Instanz gemäß dem so­ge­nann­ten Decorator Design Pattern verwenden. Das zu den 23 GoF-Design-Patterns zählende Muster er­mög­licht eine dy­na­mi­sche Er­wei­te­rung von Klassen, während die Software läuft. Diese kommt dann ohne endlos lange, schwer zu über­bli­cken­de Ver­er­bungs­hier­ar­chien aus.

Im Folgenden erfahren Sie, was genau es mit dem Decorator Pattern auf sich hat und welche Vor- und Nachteile dieses bietet. Zudem soll die Funk­ti­ons­wei­se des Musters anhand einer gra­fi­schen Dar­stel­lung und eines konkreten Beispiels ver­deut­licht werden.

Was ist das Decorator Pattern (Decorator-Ent­wurfs­mus­ter)?

Das Decorator Design Pattern, kurz Decorator Pattern (dt. Decorator-Muster), ist eine 1994 ver­öf­fent­lich­te Mus­ter­stra­te­gie für die über­sicht­li­che Er­wei­te­rung von Klassen in ob­jekt­ori­en­tier­ter Com­pu­ter­soft­ware. Nach dem Muster lässt sich jedes beliebige Objekt um ein ge­wünsch­tes Verhalten ergänzen, ohne dabei das Verhalten anderer Objekte derselben Klasse zu be­ein­flus­sen. Struk­tu­rell ähnelt das Decorator Pattern stark dem „Chain of re­spon­si­bi­li­ty“-Pattern (dt. Zu­stän­dig­keits­ket­te), wobei Anfragen anders als bei diesem Zu­stän­dig­keits­kon­zept mit zentralem Be­ar­bei­ter von allen Klassen ent­ge­gen­ge­nom­men werden.

Die Software-Kom­po­nen­te, die erweitert werden soll, wird nach dem Decorator-Ent­wurfs­mus­ter mit einer bzw. mehreren Decorator-Klassen „dekoriert“, die die Kom­po­nen­te voll­stän­dig um­schlie­ßen. Jeder Decorator ist dabei vom selben Typ wie die um­schlos­se­ne Kom­po­nen­te und verfügt damit über die gleiche Schnitt­stel­le. Dadurch kann er ein­ge­hen­de Me­tho­den­auf­ru­fe un­kom­pli­ziert an die ver­knüpf­te Kom­po­nen­te de­le­gie­ren, während er wahlweise zuvor bzw. an­schlie­ßend das eigene Verhalten ausführt. Auch eine direkte Ver­ar­bei­tung eines Aufrufs im Decorator ist grund­sätz­lich möglich.

Welchen Zweck erfüllt das Decorator Design Pattern?

Wie andere GoF-Muster, etwa das Strategy Pattern oder das Builder Pattern, verfolgt das Decorator Pattern das Ziel, Kom­po­nen­ten ob­jekt­ori­en­tier­ter Software flexibler und einfacher wie­der­ver­wend­bar zu gestalten. Zu diesem Zweck liefert der Ansatz die Lösung dafür, Ab­hän­gig­kei­ten zu einem Objekt dynamisch und – insofern er­for­der­lich – während der Laufzeit hin­zu­fü­gen bzw. entfernen zu können. Ins­be­son­de­re aus diesem Grund stellt das Pattern eine gute Al­ter­na­ti­ve zum Einsatz von Sub­klas­sen dar: Diese können eine Klasse zwar ebenfalls auf viel­fäl­ti­ge Weise ergänzen, lassen jedoch keinerlei An­pas­sun­gen während der Laufzeit zu.

Hinweis

Eine Software-Kom­po­nen­te lässt sich durch beliebig viele Decorator-Klassen erweitern. Für zu­grei­fen­de Instanzen bleiben diese Er­wei­te­run­gen dabei gänzlich un­sicht­bar, sodass diese gar nicht mit­be­kom­men, dass der ei­gent­li­chen Klasse zu­sätz­li­che Klassen vor­ge­schal­tet sind.

Decorator Pattern: UML-Diagramm zur Ver­an­schau­li­chung

Der Decorator bzw. die Decorator-Klassen (Con­cre­te­De­co­ra­tor) verfügen über die gleiche Schnitt­stel­le wie die zu de­ko­rie­ren­de Software-Kom­po­nen­te (Con­cre­te­Kom­po­nen­te) und sind vom gleichen Typ. Dieser Umstand ist wichtig für das Handling der Aufrufe, die entweder un­ver­än­dert oder verändert wei­ter­ge­lei­tet werden, falls der Decorator die Ver­ar­bei­tung nicht selbst übernimmt. Im Decorator-Pattern-Konzept be­zeich­net man diese ele­men­ta­re Schnitt­stel­le, die im Prinzip eine abstrakte Su­per­klas­se ist, als „Kom­po­nen­te“.

Das Zu­sam­men­spiel von Ba­sis­kom­po­nen­te und Decorator lässt sich am besten in einer gra­fi­schen Dar­stel­lung der Be­zie­hun­gen in Form eines UML-Klas­sen­dia­gramms ver­deut­li­chen. In der nach­fol­gen­den abs­trak­ten Abbildung des Decorator Design Patterns haben wir daher die Mo­del­lie­rungs­spra­che für ob­jekt­ori­en­tier­te Pro­gram­mie­rung verwendet.

Die Vorteile und Nachteile des Decorator Patterns im Überblick

Das Decorator-Muster bei der Kon­zep­tio­nie­rung einer Software zu be­rück­sich­ti­gen, zahlt sich gleich aus mehreren Gründen aus. Allen voran steht das hohe Maß an Fle­xi­bi­li­tät, das mit einer solchen Decorator-Struktur ein­her­geht: Sowohl zur Kom­pi­lie­rungs- als auch zur Laufzeit lassen sich Klassen gänzlich ohne Vererbung um neues Verhalten erweitern. Zu un­über­sicht­li­chen Ver­er­bungs­hier­ar­chien kommt es bei diesem Pro­gram­mier­an­satz folglich nicht, was nebenbei auch die Les­bar­keit des Pro­gramm­codes deutlich ver­bes­sert.

Dadurch, dass die Funk­tio­na­li­tät auf mehrere Decorator-Klassen auf­ge­teilt wird, lässt sich außerdem die Per­for­mance der Software steigern. So kann man gezielt jene Funk­tio­nen aufrufen und in­iti­ie­ren, die man gerade benötigt. Bei einer komplexen Ba­sis­klas­se, die alle Funk­tio­nen permanent be­reit­stellt, hat man diese res­sour­cen­op­ti­mier­te Mög­lich­keit nicht.

Die Ent­wick­lung nach dem Decorator Pattern bringt jedoch nicht nur Vorteile mit sich: Mit der Ein­füh­rung des Musters steigt au­to­ma­tisch auch die Kom­ple­xi­tät der Software. Ins­be­son­de­re die Decorator-Schnitt­stel­le ist in der Regel sehr wortreich sowie mit vielen neuen Be­griff­lich­kei­ten verknüpft und damit alles andere als ein­steig­er­freund­lich. Ein weiterer Nachteil besteht in der hohen Anzahl an Decorator-Objekten, für die eine eigene Sys­te­ma­ti­sie­rung zu empfehlen ist, um nicht mit ähnlichen Über­sichts­pro­ble­men wie bei der Arbeit mit Sub­klas­sen kon­fron­tiert zu werden. Die oft sehr langen Auf­ruf­ket­ten der de­ko­rier­ten Objekte (also der er­wei­ter­ten Software-Kom­po­nen­ten) er­schwe­ren zudem das Auffinden von Fehlern und damit den Debugging-Prozess im All­ge­mei­nen.

Vorteile Nachteile
Hohes Maß an Fle­xi­bi­li­tät Hohe Kom­ple­xi­tät der Software (ins­be­son­de­re der Decorator-Schnitt­stel­le)
Funk­ti­ons­er­wei­te­rung von Klassen ohne Vererbung Ein­stei­ger­un­freund­lich
Gut lesbarer Pro­gramm­code Hohe Anzahl an Objekten
Res­sour­cen­op­ti­mier­te Funk­ti­ons­auf­ru­fe Er­schwer­ter Debugging-Prozess

Decorator Design Pattern: Typische Ein­satz­sze­na­ri­en

Das Decorator Pattern liefert die Basis für dy­na­mi­sche und trans­pa­rent er­wei­ter­ba­re Objekte einer Software. Ins­be­son­de­re die Kom­po­nen­ten gra­fi­scher Be­nut­zer­ober­flä­chen (GUIs) sind ein typisches Ein­satz­ge­biet des Musters: Soll bei­spiels­wei­se ein Textfeld mit einer Umrandung versehen werden, genügt ein ent­spre­chen­der Decorator, der „un­sicht­bar“ zwischen das Textfeld-Objekt und den Aufruf ge­schal­tet wird, um dieses neue Interface-Element ein­zu­fü­gen.

Ein sehr bekanntes Beispiel für die Umsetzung des Decorator-Ent­wurfs­mus­ters sind die so­ge­nann­ten Stream-Klassen der Java-Bi­blio­thek, die für das Handling der Ein- und Ausgabe von Daten ver­ant­wort­lich sind. Decorator-Klassen werden hier ins­be­son­de­re dazu verwendet, um dem Da­ten­strom neue Ei­gen­schaf­ten und Sta­tus­in­for­ma­tio­nen hin­zu­zu­fü­gen oder um neue Schnitt­stel­len be­reit­zu­stel­len.

Java ist aber natürlich nicht die einzige Pro­gram­mier­spra­che, in der der Einsatz des Decorator Patterns ver­brei­tet ist. Auch folgende Sprachen setzen auf das De­sign­mus­ter:

  • C++
  • C#
  • Go
  • Ja­va­Script
  • Python
  • PHP

Pra­xis­bei­spiel für die Umsetzung des Decorator Patterns

Die Auf­lis­tung der Vor- und Nachteile zeigt, dass das Decorator Design Pattern nicht für jede Art von Software geeignet ist. Wo eine Klasse im Nach­hin­ein noch verändert werden soll und ins­be­son­de­re in Projekten, in denen dies nicht mithilfe von Sub­klas­sen zu rea­li­sie­ren ist, stellt das De­sign­mo­dell jedoch eine erst­klas­si­ge Lösung dar. Ein gutes Beispiel, das den Nutzen der „de­ko­rier­ten“ Klassen unter Beweis stellt, liefert Marcel Schöni in folgendem Artikel seines ZKMA-Blogs.

Aus­gangs­la­ge ist in diesem Fall eine Software, die die Namen von Personen über die abstrakte Klasse Mit­ar­bei­ter“ abrufbar macht. Der erste Buchstabe der ab­ge­ru­fe­nen Namen ist jedoch immer klein­ge­schrie­ben. Da eine nach­träg­li­che Anpassung unmöglich ist, wird die Decorator-KlasseMit­ar­bei­ter­De­co­ra­tor“ im­ple­men­tiert, die über die gleiche Schnitt­stel­le operiert und ebenfalls den Aufruf der Methode getName() er­mög­licht. Zu­sätz­lich erhält der Decorator eine Logik, die si­cher­stellt, dass der erste Buchstabe kor­rek­ter­wei­se groß­ge­schrie­ben wird. Das passende Code-Beispiel sieht dabei fol­gen­der­ma­ßen aus:

public class MitarbeiterDecorator implements Person {
private Mitarbeiter mitarbeiter;
public MitarbeiterDecorator(Mitarbeiter mitarbeiter){
    this.mitarbeiter = mitarbeiter;
}
public String getName(){
    // rufe die Methode der Mitarbeiterklasse auf
    String name = mitarbeiter.getName();
    // Stelle hier sicher, dass der erste Buchstabe groß ist
    name = Character.toUpperCase(name.charAt(0)) 
    + name.substring(1, name.length());
    return name;
}
}
Zum Hauptmenü