Kompositum (Entwurfsmuster)

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Das Kompositum (englisch composite oder whole-part) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung, das zur Kategorie der Strukturmuster (englisch structural patterns) gehört. Es ist ein so genanntes GoF-Entwurfsmuster. Das Kompositionsmuster (composite pattern) wird angewendet, um Teil-Ganzes-Hierarchien zu repräsentieren, indem Objekte zu Baumstrukturen zusammengefügt werden.[1] Die Grundidee des Kompositionsmusters ist, in einer abstrakten Klasse sowohl primitive Objekte als auch ihre Behälter zu repräsentieren. Somit können sowohl einzelne Objekte als auch ihre Kompositionen einheitlich behandelt werden.

Verwendung[Bearbeiten | Quelltext bearbeiten]

  • Implementierung von Teil-Ganzes-Hierarchien.
  • Verbergen der Unterschiede zwischen einzelnen und zusammengesetzten Objekten.

Ein typisches Beispiel für ein Kompositum sind hierarchische Dateisysteme, insbesondere ihre Repräsentation innerhalb von Dateimanagern oder Datei-Browsern als Verzeichnisse und Dateien.

Ein anderes Beispiel sind die Klassendefinitionen der grafischen Benutzeroberfläche von Java. Alle Elemente wie Schaltflächen und Textfelder sind Spezialisierungen der Klasse Component. Die Behälter für diese Elemente sind aber ebenfalls Spezialisierungen derselben Klasse. Mit anderen Worten: Alle Standardelemente werden wesentlich durch eine einzige (Kompositum-)Klasse definiert.

UML-Diagramme[Bearbeiten | Quelltext bearbeiten]

Klassendiagramm[Bearbeiten | Quelltext bearbeiten]

Klassendiagramm
Klassendiagramm

Objektdiagramm[Bearbeiten | Quelltext bearbeiten]

Objektdiagramm
Objektdiagramm

Bestandteile[Bearbeiten | Quelltext bearbeiten]

Die Komponente definiert als Basisklasse das gemeinsame Verhalten aller Teilnehmer. Sie ist im Allgemeinen abstrakt und zum Beispiel ein Verzeichniseintrag.

Das Blatt repräsentiert ein Einzelobjekt, es besitzt keine Kindobjekte und ist zum Beispiel in einem Dateiverzeichnis eine Datei.

Das Kompositum enthält Komponenten, also weitere Komposita oder auch Blätter, als Kindobjekte und repräsentiert zum Beispiel ein Verzeichnis.

Vorteile[Bearbeiten | Quelltext bearbeiten]

  • einheitliche Behandlung von Primitiven und Kompositionen
  • leichte Erweiterbarkeit um neue Blatt- oder Container-Klassen

Nachteile[Bearbeiten | Quelltext bearbeiten]

Ein zu allgemeiner Entwurf erschwert es, Kompositionen auf bestimmte Klassen (und damit zumeist Typen) zu beschränken. Das Typsystem der Programmiersprache bietet dann keine Hilfe mehr, so dass Typüberprüfungen zur Laufzeit nötig werden.

Beispiele[Bearbeiten | Quelltext bearbeiten]

C++[Bearbeiten | Quelltext bearbeiten]

Diese C++14 Implementierung basiert auf dem Beispielcode im Buch Entwurfsmuster.

#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <stdexcept>

typedef double Betrag;

// deklariert die Schnittstelle für Objekte in der zusammengefügten Struktur.
class Geraet { // Komponente
public:
  // implementiert, sofern angebracht, ein Defaultverhalten für die allen Klassen gemeinsame Schnittstelle.
  virtual const std::string& getName() {
    return name;
  }
  virtual void setName(const std::string& name_) {
    name = name_;
  }
  virtual Betrag getNettoPreis() {
    return nettoPreis;
  }
  virtual void setNettoPreis(Betrag nettoPreis_) {
    nettoPreis = nettoPreis_;
  }
  // deklariert eine Schnittstelle zum Zugriff auf und zur Verwaltung von Kindobjektkomponenten.
  virtual void fuegeHinzu(std::shared_ptr<Geraet>) = 0;
  virtual void entferne(std::shared_ptr<Geraet>) = 0;
  virtual ~Geraet() = default;
protected:
  Geraet() :name(""), nettoPreis(0) {}
  Geraet(const std::string& name_) :name(name_), nettoPreis(0) {}
private:
  std::string name;
  Betrag nettoPreis;
};

// definiert Verhalten für Komponenten, die Kindobjekte haben können.
class ZusammengesetztesGeraet : public Geraet { // Kompositum
public:
  // implementiert kindobjekt-bezogene Operationen der Schnittstelle von Komponente.
  virtual Betrag getNettoPreis() override {
    Betrag gesamt = Geraet::getNettoPreis();
    for (const auto& i:teile) {
      gesamt += i->getNettoPreis();
    }
    return gesamt;
  }
  virtual void fuegeHinzu(std::shared_ptr<Geraet> geraet_) override {
    teile.push_front(geraet_.get());
  }
  virtual void entferne(std::shared_ptr<Geraet> geraet_) override {
    teile.remove(geraet_.get());
  }
protected:
  ZusammengesetztesGeraet() :teile() {}
  ZusammengesetztesGeraet(const std::string& name_) :teile() {
    setName(name_);
  }
private:
  // speichert Kindobjektkomponenten.
  std::list<Geraet*> teile;
};

// repräsentiert Blattobjekte in der Komposition.
class FloppyDisk : public Geraet { // Blatt
public:
  FloppyDisk(const std::string& name_) {
    setName(name_);
  }
  // Ein Blatt besitzt keine Kindobjekte.
  void fuegeHinzu(std::shared_ptr<Geraet>) override {
    throw std::runtime_error("FloppyDisk::fuegeHinzu");
  }
  void entferne(std::shared_ptr<Geraet>) override {
    throw std::runtime_error("FloppyDisk::entferne");
  }
};

class Gehaeuse : public ZusammengesetztesGeraet {
public:
  Gehaeuse(const std::string& name_) {
    setName(name_);
  }
};

int main() {
  // Die Smart pointers verhindern Memory Leaks.
  std::shared_ptr<FloppyDisk> fd1 = std::make_shared<FloppyDisk>("3.5in Floppy");
  fd1->setNettoPreis(19.99);
  std::cout << fd1->getName() << ": nettoPreis=" << fd1->getNettoPreis() << '\n';

  std::shared_ptr<FloppyDisk> fd2 = std::make_shared<FloppyDisk>("5.25in Floppy");
  fd2->setNettoPreis(29.99);
  std::cout << fd2->getName() << ": nettoPreis=" << fd2->getNettoPreis() << '\n';

  std::unique_ptr<Gehaeuse> gh = std::make_unique<Gehaeuse>("PC Gehaeuse");
  gh->setNettoPreis(39.99);
  gh->fuegeHinzu(fd1);
  gh->fuegeHinzu(fd2);
  std::cout << gh->getName() << ": nettoPreis=" << gh->getNettoPreis() << '\n';

  fd2->fuegeHinzu(fd1);
}

Die Programmausgabe ist:

3.5in Floppy: nettoPreis=19.99
5.25in Floppy: nettoPreis=29.99
PC Gehaeuse: nettoPreis=89.97
terminate called after throwing an instance of 'std::runtime_error'
  what():  FloppyDisk::fuegeHinzu

Java[Bearbeiten | Quelltext bearbeiten]

Javas AWT-Klassen sind nach dem Kompositum-Muster gebaut. Da alle von Container erben, können sie jeweils selbst wieder Elemente aufnehmen.

Das folgende Beispiel besteht aus einer Grafik-Klasse; eine Grafik kann eine Ellipse oder eine Komposition von vielen Grafiken sein. Jede Grafik implementiert eine Methode zum Ausdrucken.

Es könnten noch weitere Figuren (Rechteck etc.) oder weitere Methoden (etwa „Rotiere“) implementiert werden.

/** "Komponente" */
interface Graphic {

    //Prints the graphic.
     void print();
}

/** "Komposition" */
class CompositeGraphic implements Graphic {

    //Collection of child graphics.
    private List<Graphic> childGraphics = new ArrayList<Graphic>();

    //Prints the graphic.
    @Override
    public void print() {
        for (Graphic graphic : childGraphics) {
            graphic.print();
        }
    }

    //Adds the graphic to the composition.
    public void add(Graphic graphic) {
        childGraphics.add(graphic);
    }

    //Removes the graphic from the composition.
    public void remove(Graphic graphic) {
        childGraphics.remove(graphic);
    }
}

/** "Leaf" */
class Ellipse implements Graphic {

    //Prints the graphic.
    @Override
    public void print() {
        System.out.println("Ellipse");
    }
}

/** Client */
public class Program {

    public static void main(String[] args) {
        //Initialize four ellipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();

        //Initialize three composite graphics
        CompositeGraphic graphic = new CompositeGraphic();
        CompositeGraphic graphic1 = new CompositeGraphic();
        CompositeGraphic graphic2 = new CompositeGraphic();

        //Composes the graphics
        graphic1.add(ellipse1);
        graphic1.add(ellipse2);
        graphic1.add(ellipse3);

        graphic2.add(ellipse4);

        graphic.add(graphic1);
        graphic.add(graphic2);

        //Prints the complete graphic (four times the string "Ellipse").
        graphic.print();
    }
}

Verwendung in der Analyse[Bearbeiten | Quelltext bearbeiten]

Ein Kompositum ist auch als reines Daten-Muster interessant, ohne dass Operationen in den Klassen definiert werden, da es zur Repräsentation allgemeiner Baumstrukturen verwendet werden kann. Daher ist dieses Muster auch in der Analyse sinnvoll einsetzbar, z. B. zur Darstellung verschachtelter Aufträge oder Auftragnehmer (mit Unteraufträgen/Unterauftragnehmern), verschachtelter Abläufe, hierarchischer Gruppen von Dingen (Benutzergruppen, E-Mail-Listen, Artikelgruppen, organisatorische Verbünde) usw. Es muss aber darauf geachtet werden, ob solche Hierarchien tatsächlich gleichförmig sind, oder ob die inneren Ebenen verschiedene fachliche Bedeutung haben. Letzteres drückt sich z. B. darin aus, dass Begriffe wie „Gruppe“ und „Untergruppe“ fachlich unterschieden werden.

Verwandte Entwurfsmuster[Bearbeiten | Quelltext bearbeiten]

  • Visitor
  • Decorator
  • Ein Design auf Basis des Kommando-Musters kann oft sinnvollerweise auch zusammengesetzte Kommandos enthalten, die nach dem Kompositum-Muster aufgebaut sind.

Weblinks[Bearbeiten | Quelltext bearbeiten]

Einzelnachweise[Bearbeiten | Quelltext bearbeiten]

  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. 5. Auflage. Addison-Wesley, 1996, ISBN 3-8273-1862-9, S. 239.