zurück zum Artikel

Patterns in der Softwareentwicklung: Das Decorator-Muster

Rainer Grimm

(Bild: Kenneth Summers / shutterstock.com)

Die Aufgabe des Decorator-Musters ist es, ein Objekt dynamisch mit Verantwortlichkeiten zu erweitern.

Patterns sind eine wichtige Abstraktion in der modernen Softwareentwicklung. Sie bieten eine klar definierte Terminologie, eine saubere Dokumentation und das Lernen von den Besten. Das klassische Buch "Design Patterns: Elements of Reusable Object-Oriented Software" (kurz Design Patterns), das ich in meiner Serie über Muster vorstelle, enthält 23 Muster. Das Decorator-Muster ist das dritte Strukturmuster aus dem Buch. Es erweitert ein Objekt dynamisch mit Verantwortlichkeiten.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Die ersten beiden Strukturmuster sind das Adapter-Muster [1] und das Bridge-Muster [2]. Man sollte das Decorator-Muster nicht mit dem Decorator-Idiom in Python verwechseln. Die beiden verfolgen unterschiedliche Ziele: Mit dem Decorator-Muster kann man Objekte dynamisch erweitern, mit dem Decorator-Idiom hingegen Funktionen dynamisch erweitern.

Hier sind die Fakten zum Decorator-Muster.

Zweck

Erweitert ein Objekt dynamisch mit Verantwortlichkeiten

Auch bekannt als

Wrapper

Anwendungsfall

Component

ConcreteComponent

Decorator

ConcreteDecorator

Eine wichtige Beobachtung des Decorator-Musters ist, dass mehrere Dekoratoren übereinander gesteckt werden können, wobei jeder Dekorator neue Funktionen zu den überschriebenen Mitgliedsfunktionen hinzufügt.

Das folgende Beispiel basiert auf dem Beispiel auf der Wikipedia-Seite Decorator Pattern [3].

// decorator.cpp 
// (based on https://en.wikipedia.org/wiki/Decorator_pattern)

#include <iostream>
#include <string>

struct Shape {
  virtual ~Shape() = default;

  virtual std::string GetName() const = 0;
};

struct Circle : Shape {
  void Resize(float factor) { radius *= factor; }

  std::string GetName() const override {
    return std::string("A circle of radius ") + 
      std::to_string(radius);
  }

  float radius = 10.0f;
};

struct ColoredShape : Shape {
  ColoredShape(const std::string& color, Shape* shape)    // (1)
      : color(color), shape(shape) {}

  std::string GetName() const override {
    return shape->GetName() + " which is colored " + 
    color + ".";                                           // (2)
  }

  std::string color;
  Shape* shape;
};

int main() {

  std::cout << '\n';

  Circle circle;
  ColoredShape colored_shape("red", &circle);
  std::cout << colored_shape.GetName() << '\n';

  std::cout << '\n';

}

In diesem Beispiel ist Shape die Component. Circle steht für die ConcreteComponent und ColoredShape für den Decorator. ColoredShape erhöht sich Shape in seinem Konstruktor (1), ruft in (2) die Mitgliedsfunktion shape->GetName() auf und dekoriert es mit seiner Farbe.

Hier ist die Ausgabe des Programms:

Wenn man ein FramedShape als zusätzlichen Decorator ableitet, kannt man sie auf beliebige Weise zusammenstecken:

// decoratorFrame.cpp 
// (based on https://en.wikipedia.org/wiki/Decorator_pattern)

#include <iostream>
#include <string>

struct Shape{
  virtual std::string str() const = 0;
};

class Circle : public Shape{
  float radius = 10.0f;
  
public:
  std::string str() const override{
    return std::string("A circle of radius ") + 
      std::to_string(radius);
  }
};

class ColoredShape : public Shape{
  std::string color;
  Shape& shape;
public:
  ColoredShape(std::string c, Shape& s): color{c}, shape{s} {}
  std::string str() const override{
    return shape.str() + std::string(" which is coloured ") +
      color;
  }
};

class FramedShape : public Shape{
  Shape& shape;
public:
  FramedShape(Shape& s): shape{s} {}
  std::string str() const override{
    return shape.str() + std::string(" and has a frame");
  }
};

int main(){

  Circle circle;
  ColoredShape coloredShape("red", circle);    // (1)
  FramedShape framedShape1(circle);            // (2)
  FramedShape framedShape2(coloredShape);      // (3)
  
  std::cout << circle.str() << '\n';
  std::cout << coloredShape.str() << '\n';
  std::cout << framedShape1.str() << '\n';
  std::cout << framedShape2.str() << '\n';

}

Der ColoredShape nimmt einen Circle (1), der FramedShape einen Circle (2) oder einen ColoredShape (3) an. Die entsprechenden Mitgliedsfunktionen str zeigen die verschiedenen Kombinationen an.

Verwandte Muster

Vorteile

Nachteile

Das Composite-Muster [8] ist ein Strukturmuster und dem Decorator-Muster sehr ähnlich. Der Hauptunterschied besteht darin, dass das Letzteres nur ein Kind hat. Außerdem fügt es einem Objekt neue Verantwortung hinzu, während das Composite-Muster die Ergebnisse seiner Kinder zusammenfasst. (rme [9])


URL dieses Artikels:
https://www.heise.de/-7315588

Links in diesem Artikel:
[1] https://heise.de/-7285630
[2] https://heise.de/-7308881
[3] https://en.wikipedia.org/wiki/Decorator_pattern
[4] https://en.wikipedia.org/wiki/Composite_pattern
[5] https://heise.de/-7285630
[6] https://heise.de/-7308881
[7] https://en.wikipedia.org/wiki/Strategy_pattern
[8] https://en.wikipedia.org/wiki/Composite_pattern
[9] mailto:rme@ix.de