Benutzer-Werkzeuge

Webseiten-Werkzeuge


listen:kompositum:start

Dies ist eine alte Version des Dokuments!


Entwurfsmuster allgemein

Entwurfsmuster (englisch design patterns) sind bewährte Lösungsschablonen (hier: Klassen und deren Beziehungen) für wiederkehrende Entwurfsprobleme. Ihre Verwendung sorgt für leichter wartbaren Programmcode. Sie reduziert damit den Aufwand und auch die Kosten bei der Softwareentwicklung.

Ausgangspunkt der Verwendung von Entwurfsmustern in der Softwaretechnik war die Veröffentlichung des Buches "Design Patterns" durch Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides im Jahr 1994. Sie tragen den Ehrennamen "Gang of four", entsprechend ist das Buch als "GoF-Book" bekannt. Auch das Entwurfsmuster "Kompositum" wird in diesem Buch beschrieben.

Implementierung einer Liste mit dem Entwurfsmuster "Kompositum"

Der Nachfolger des letzten Elementes der Liste soll nicht mehr den Wert null erhalten, sondern auf ein Abschluss-Objekt zeigen. Das wird uns etwas später die Möglichkeit geben, auf elegante Art und Weise ohne den fehlerträchtigen Wert null auszukommen. Sehen wir uns eine beispielhafte Liste mit vier Elementen im Objektdiagramm an:

Damit das Attribut nachfolger sowohl auf ein Knoten-Objekt als auch auf ein Abschluss-Objekt zeigen kann, muss sein Datentyp eine Oberklasse von Knoten und Abschluss sein. Wir nennen diese Klasse Listenelement und implementieren sie als abstrakte Klasse. Alle Methoden der Klasse Listenelement sind abstrakt, d.h. sie werden dort nur deklariert, aber nicht mit Anweisungen gefüllt. Das geschieht erst in den Klassen Knoten und Abschluss – und zwar auf unterschiedliche Weise!

Klassendiagramm

Implementierung

Die Klasse Inhalt soll beispielhaft einfach nur Text speichern können:

public class Inhalt {
 
   private String text;
 
   public Inhalt(String text) {
      this.text = text;
   }
 
   public void ausgabe() {
      println(text);
   }
}

Die Klasse Listenelement besitzt drei abstrakte Methoden (d.h. Methoden, die erst in Unterklassen definiert werden):

abstract class Listenelement {
   public abstract Inhalt getInhalt();                         // gibt den Inhalt zurück (null im Falle des Abschlusses)
   public abstract Listenelement getNachfolger();              // gibt das nachfolgende Element zurück (null im Falle des Abschlusses)
   public abstract Listenelement hintenAnfügen(Inhalt inhalt); // fügt den Inhalt am Ende der Liste an. 
 
   // Bemerkung:
   // Bei Knoten-Objekten gibt die Methode hintenAnfügen das Objekt selbst wieder zurück.
   // Bei Abschluss-Objekten gibt die Methode das neue eingefügte Knoten-Objekt zurück.
}

Die Klasse Liste speichert eine Referenz "erster" auf das erste Element der Liste. Ihre Methoden machen regen Gebrauch von den Methoden dieses Objekts.

class Liste {
 
   private Listenelement erster;    // Zeigt auf das erste Listenelement
 
   public Liste() {
      erster = new Abschluss();     // Die leere Liste besteht nur aus einem Abschluss-Objekt
   }
 
   public Inhalt erstenEntnehmen() { 
      Listenelement alterErster = erster;
      erster = erster.getNachfolger();
      return alterErster.getInhalt();
   }
 
   public void hintenAnfügen(Inhalt inhalt) {
      // Genau dann, wenn erster ein Abschluss-Objekt ist, wird die
      // folgende Anweisung den Wert von erster ändern.
      erster = erster.hintenAnfügen(inhalt);
   }
}

Ein Objekt der Klasse Knoten speichert eine Referenz auf ein Inhalt-Objekt sowie eine Referenz auf das nachfolgende Knoten-Objekt (bzw. ein Abschluss-Objekt, wenn der Knoten das letzte Element der Liste ist).

class Knoten extends Listenelement {
   private Inhalt inhalt;
   private Listenelement nachfolger;
 
   public Listenelement getNachfolger() {
      return nachfolger;
   }
 
   public Knoten(Inhalt inhalt, Knoten nachfolger) {
      this.inhalt = inhalt;
      this.nachfolger = nachfolger;
   }
 
   public Knoten(Inhalt inhalt, Listenelement l) {
      this.inhalt = inhalt;
      nachfolger = l;
   }
 
   public Inhalt getInhalt() {
      return inhalt;
   }
 
   public Listenelement hintenAnfügen(Inhalt inhalt) {
      nachfolger = nachfolger.hintenAnfügen(inhalt);     // "Soll sich doch das Nachfolger-Objekt darum kümmern!" :-)
      return this;
   }
 
}

Das Abschluss-Objekt der Liste hat keinen Inhalt. Seine Hauptaufgabe besteht darin, sich in der Methode hintenAnfügen um die Erzeugung und das Einflicken eines neuen Knoten-Objekts in die Liste zu kümmern.

class Abschluss extends Listenelement {
   public Inhalt getInhalt() {
      return null;
   }
 
   public Listenelement getNachfolger() {
      return this;
   }
 
   public Listenelement hintenAnfügen(Inhalt inhalt) {
      Knoten k = new Knoten(inhalt, this);        // erzeugt einen neuen Knoten, dessen nachfolger-Objekt dieses Abschluss-Objekt ist
      return k;                                   // gibt eine Referenz auf den neuen Knoten an das aufrufende Objekt zurück
  	}
}

Das ganze Programm zum Ausprobieren

Aufgaben zur Listenimplementierung mit Kompositum

Trennung von Struktur und Inhalt

Im obigen Beispiel muss die Inhaltsklasse nicht verändert werden, wenn die Funktionalitäten der Liste programmiert werden. Das Prinzip "Trennung von Struktur und Inhalt" ist damit erfüllt.

Bemerkung: Die Trennung von Struktur und Inhalt wäre nicht gegeben, wenn man der Inhaltsklasse selbst das Attribut "nachfolger" und die Methoden getNachfolger sowie hintenAnfügen hinzugefügt hätte.

Das Entwurfsmuster "Kompositum"

Die Kernidee des Entwurfsmusters "Kompositum" besteht darin, dass in einer zusammengesetzten Datenstruktur die "innen liegenden" Objekte (Fachbegriff: Kompositum, im obigen Beispiel: Knoten) und die "außen liegenden" Objekte (Fachbegriff: Blatt, im obigem Beispiel: Abschluss) Unterklassen einer abstrakten Klasse (Fachbegriff: Komponente, im obigen Beispiel: Listenelement) sind. In dieser abstrakten Klasse werden alle Methoden deklariert, die zum Verändern der Datenstruktur und zum Zugriff auf einzelne Elemente benötigt werden. Dadurch können die Kompositum-Objekte und die Blatt-Objekte im Hauptprogramm "gleich behandelt" werden.

Das Entwurfsmuster Kompositum (engl. composite) dient als Lösungsansatz für Situationen, in denen sowohl einfache als auch zusammengesetzte Elemente gleichbehandelt werden sollen und in denen eine Teil-Ganzes-Beziehung vorliegt. Es wird allgemein durch folgendes Klassendiagramm veranschaulicht:
Bemerkung: Die Bezeichnung "Blatt" in diesem Diagramm ist recht unglücklich, da sie nicht übereinstimmt mit dem Fachbegriff "Blatt" für außenliegende Knoten eines Baumes (siehe das Kapitel über Bäume). Letzterer wird weit häufiger gebraucht. Im Englischen ist die Nomenklatur geringfügig besser: Leaf bezeichnet die Blatt-Klasse im Entwurfsmuster Kompositum, leaf node einen außenliegenden Knoten eines Baumes.

Beispiel: Dateisystem

Eine Möglichkeit zur anschaulichen Anwendung des Kompositums auf eine vertraute Struktur bietet das Dateisystem, das auf einem Laufwerk (Festplatte, USB-Stick, …) zu finden ist: Wie gewohnt kann ein Laufwerk hier Dateien und Ordner enthalten, ein Ordner wiederum (Unter-)Ordner und Dateien (die keine weiteren Objekte enthalten). Gemeinsame Methoden könnten z.B. der Ermittlung der Größe von Dateien und Ordnern dienen. Die Klasse Laufwerk steht außerhalb des Entwurfsmusters Kompositum.

Vorteile/Nachteile

Gegenüber einer Implementierung mit Knoten-Objekten aber ohne Abschluss-Objekt ergeben sich folgende Vorteile/Nachteile:

Vorteile:

  • Gleichbehandlung der unterschiedlichen Listenbestandteile möglich
  • Keine Überprüfung auf null-Werte mehr
  • Stärkere Strukturierung

Nachteile:

  • Programmieraufwand ist höher
  • Mehr beteiligte Klassen
  • deutlich schlechtere Performance
listen/kompositum/start.1726739884.txt.gz · Zuletzt geändert: 2024/09/22 04:37 (Externe Bearbeitung)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki