Back To Top

Entwurfsmuster (Design Patterns) in C#.NET: Dekorierer

Design Patterns: Dekorierer / Decorator in C#.NET

Comelio-Blog C#.NET DekoriererUm ein Beispiel für die Verwendung von abstrakten Klassen zu geben und um gleichzeitig auch noch ein weiteres Entwurfsmuster vorzustellen, greifen wir auf das Dekorierer-Muster zurück. Nehmen wir sofort ganz konkret an, dass die modellierten Produkte einer Shop-Anwendung in verschiedenen Arten wie HTML, XML, Text ausgegeben werden sollen.

Dazu lassen sich zwei grundlegende Vorgehensweise denken:

  1. Die eine Möglichkeit basiert darauf, jedem Produkt seine individuelle Ausgabeart mit Hilfe einer passend benannten Methode wie liefereHTML oder liefereXML mit auf seinen Weg zu geben. Dies verhindert allerdings, dass man die Ausgabevarianten wirklich dynamisch ausführen kann, weil dann bspw. im Warenkorb unterschiedliche Methoden aufgerufen und bekannt sein müssen, wenn mal die eine oder mal die andere Ausgabeart erfolgen soll. Eine Fallunterscheidung ist hier in jedem Fall nötig. Bei wachsenden Ausgabearten müssen zudem die Klassen angepasst, weil die neue Methoden integriert werden müssen. Sind diese zusätzlich in einer abstrakten Klasse oder einer Schnittstelle bekannt, kann es sogar sein, dass Methoden implementiert werden müssen, die gar nicht weiter nützlich für die Arbeit mit dem konkreten Objekt sind, aber von der Schnittstelle her gefordert werden. Zusätzlich sind durch Änderungen, die direkt im Klassenquelltext ausgeführt werden müssen, auch immer Risiken mit neuen Fehlern verbunden.
  2. Eine andere Möglichkeit besteht darin, in einer einzigen Methode mit einem typ-Parameter zu arbeiten und diesen in einer Fallunterscheidung auszuwerten, um die passende Ausgabeart zu erzeugen. Dies erleichtert die Arbeit mit der Klasse, da jetzt keine neuen Methoden bei neuen Ausgabearten erstellt werden müssen. Allerdings ist dies auch keine gelungene Lösung, da der Parameter von seinen Werten her kommuniziert werden muss und nicht bereits am Klassenaufbau zu erkennen ist. Dann müssen Änderungen zusätzlich immer im Quelltext direkt innerhalb der Methode in der Fallunterscheidung ausgeführt werden, wenn neue Ausgabearbeiten hinzukommen. Dies fördert wiederum die Fehleranfälligkeit von Änderungen und kann sich – nicht unbedingt in diesem, aber in anderen Fällen – so ausweiten, dass die gleiche Fallunterscheidung an mehreren Stellen bei der Arbeit mit dem Objekt eingerichtet werden muss.

Diese beiden Möglichkeiten berücksichtigen allerdings nur eine komplette Ausgaben, und nicht etwa die Zuweisung von zusätzlichen Zuständigkeiten. Für einer HTML- und einer XML-Ausgabe benötigt man noch kein Dekorierer-Muster sondern gilt zunächst nur, dass die beste Lösung die Erstellung einer eigenen abstrakten Ausgabeklasse ist, von der die individuellen Ausgaben abgeleitet und an die die einzelnen Objekte, welche ausgegeben werden sollen, übergeben werden. Was in der Situationsbeschreibung noch dazu kommt, ist nun die Forderung, dass die eigentlich gleiche Ausgabe „mal so und mal so“ herauskommen soll. Dies kann sich im klassischen Beispiel immer auf die Ausgabe von Kopf- und Fußzeilen beziehen. Dafür möchte man nun sicherlich keine extra Methoden einführen oder mit mehreren Schaltern arbeiten, die individuell voneinander mit Werten versehen werden sollen.

Das Dekorierer-Muster also bietet eine Lösung dafür, dass die Zuständigkeiten (Funktionalitäten, Werte) eines Objekts dynamisch erweitert werden können, ohne die beiden gerade genannten Grundtechniken zu verwenden. Die Klassen sollen also selbst nicht geändert werden, sondern sie soll auf andere Weise neue Fähigkeiten zugewiesen bekommen. Bei der Quelltextverwendung der Zuständigkeitenklassen (Dekorierer) und der bearbeiteten Objekten (dekorierte Objekte) wird man einen Zustand erreichen, in dem man die benötigten Zuständigkeiten passend ineinander verschachtelt und das Objekt selbst für die Dekoration übergibt und so auch einen besonders gut lesbaren Quelltext und vor allen Dingen eine dynamische Verknüpfung zu erreichen.

Für das Muster benötigt man zunächst eine „besonders“ abstrakte Klasse, in der die zu ergänzende Funktionalität als abstrakte Methode vorgegeben wird. Selbstverständlich besteht nachher noch die Möglichkeit, weitere Methoden oder auch Felder/Eigenschaften und damit weitere Zuständigkeiten zu entwickeln. Es ist allerdings wichtig, dass sowohl die einzelnen Dekorierer als auch das Dekorierte selbst von dieser abstrakten Klasse erben. In unserem Fall handelt es sich um die Methode ausgeben.

Dann folgen wiederum zwei abstrakte Klassen, von denen die eine als Oberklassenkonzept für die Produkte und die andere als Konzept für die Ausgaben fungiert. In verschiedenen anderen Beispielen in der Literatur zum gleichen Muster würde die abstrakte Klasse für das Dekorierte fehlen. Da wir in unserem Shopbeispiel allerdings verschiedene Produkte verwenden, muss sich dies jetzt auch niederschlagen. Wichtig ist hier zu bemerken, dass die abstrakte Klasse für die Dekoration eine Aggregationsbeziehung mit der abstrakten Klasse von den Dekorierten hat. Diese werden also einem Dekorierer übergeben, der sie dann dekorieren bzw. in anderer Form ausgeben soll.

// Absolute Oberklasse mit Methode ausgeben
abstract public class ProduktAusgabe {
    public abstract string ausgeben();
}
// Abstrakte Klasse für Produkte für Dekoration
abstract public class Produkt : ProduktAusgabe {
    public int nr, menge;
    public Produkt(int nr, int menge) {
        this.nr = nr;
        this.menge = menge;
    }
}
// Abstrakte Klasse für Dekoratoren
abstract public class Ausgabe : ProduktAusgabe {
    protected ProduktAusgabe produkt;
    public Ausgabe(ProduktAusgabe produkt) {
        this.produkt = produkt;
    }
}

Abstrakte Klassen

Es folgen danach beliebig viele – in unserem Abdruck nur eine einzige – Klassen möglicher Produkte, die dekoriert werden sollen. Die Methode ausgeben ist hier ebenfalls enthalten, wird allerdings nur für einfache Textausgabe verwendet.

public class CD : Produkt {
    public double minuten;
    public CD(int nr, int menge, double minuten)
        : base(nr, menge) {
        this.minuten = minuten;
    }
    public override string ausgeben() {
        return "CD: " + nr + " | " + minuten;
    }
}

Konkrete Klasse für ein CD-Produkt

Danach wiederum folgen die verschiedenen Dekorierer, von denen wir hier als Beispiel zwei erstellt haben und auch zeigen. Der eine erstellt die bereits oben erwähnte Kopf- und der andere die Fußzeile. Das heißt, die in der jeweiligen Produktklasse enthaltene ausgeben-Methode wird hier tatsächlich erweitert, indem vorher und nachher zusätzliche Ausgaben gemacht werden. Diese zusätzlichen Angaben sind genau die Zuständigkeiten, von denen in der obigen Beschreibung die Rede war, weil hier eine bereits fertige (Basis-)Implementierung mit weiteren Funktionalitäten versehen wird. Das Muster eignet sich von seinem Aufbau hier besonders gut, weil tatsächlich auch physisch um die eigentliche Ausgabezeile weitere Ausgaben gemacht werden.

public class KopfZeile : Ausgabe {
    public KopfZeile(ProduktAusgabe produkt) : base(produkt) { }
    public override string ausgeben() {
        return "<h1>Produkt</h1>\n"
               + produkt.ausgeben();
    }
}
public class FussZeile : Ausgabe {
    public FussZeile(ProduktAusgabe produkt) : base(produkt) { }
    public override string ausgeben() {
        return produkt.ausgeben() + "<hr/>\n";
    }
}

Konkrete Dekorationsklassen

Unabhängig vom Aufbau der verschiedenen Klassen ist nun besonders interessant, wie denn die Forderung, dass die Zuständigkeiten dynamisch übergeben werden, sich im Klienten-Quelltext niederschlägt. Dies erkennt man besonders einfach am konkreten Beispiel. Zwei verschiedene Objekte vom Typ Buch und CD, die jeweils von Produkt und dieses wiederum von ProduktAusgabe erben, werden erzeugt und sollen jeweils ausgegeben werden. Dabei erzeugt man ein Objekt vom Typ Ausgabe, in dem man in der richtigen Reihenfolge die Zuständigkeiten und schließlich das auszugebende Objekt selbst dem Konstruktor übergibt. Dies führt dazu, dass man besonders einfach die verschiedenen Dekorierungen erkennt und auch sehr kompakt die konkrete Ausgabe einrichten kann. Im ersten Fall erzeugt man eine mit Kopf- und Fußzeile, im zweiten genügt eine einfache Kopfzeile.

Buch roman = new Buch(1254, 1, 244);
CD album = new CD(354, 1, 45.23);
Ausgabe html1 = new KopfZeile(new FussZeile(roman));
ausgabe.Text = html1.ausgeben();
Ausgabe html2 = new KopfZeile(album);
ausgabe.Text += html2.ausgeben();

Verwendung einer Dekoration

In diesem Fall lohnt sich auch einmal ein Beweisfoto vom Browser, in dem deutlich die Überschrift in h1 und die Trennungslinie des ersten Produktes zu sehen ist.

Ausgabe im Browser

Ausgabe im Browser

Das Klassendiagramm hat einen typischen Aufbau, der in unserem Beispiel allerdings auf der linken Seite um eine weitere abstrakte Klasse ergänzt wird, da es verschiedene Arten von Produkten gibt. Beide Klassen, sowohl die Dekoriererklasse als auch die der zu dekorierenden Objekte erben von der gleichen abstrakten Klasse. Dadurch ist es möglich, einem Dekorierer sowohl ein Objekt vom Typ eines Dekorierers als auch vom Typ eines Objekts zu übergeben, weil der Konstruktor in Wirklichkeit auf den gemeinsamen Obertyp wartet.

Dekorierer

Dekorierer

» Kontaktformular










comelio.com

mail address

mail address

  • Berlin | Comelio GmbH
    Fon: +49(0)30-8145622-00
    Fax: +49(0)30-8145622-10
  • München | Comelio GmbH
    Fon: +49(0)89-38156860-0
    Fax: +49(0)89-38156860-9
  • Hamburg | Comelio GmbH
    Fon: +49(0)40-20934996-0
    Fax: +49(0)40-20934996-9
  • Wien | Comelio GmbH
    Fon: +43-720-2097-97
    Fax: +43-720-2097-98