Dy­na­mi­sche Da­ten­struk­tu­ren wie die Baum­struk­tur einer Da­tei­ver­wal­tung oder eines Programm-In­ter­faces erfordern ein klares, möglichst ein­deu­ti­ges Hier­ar­chie-Gefüge. Die Umsetzung solcher Struk­tu­ren ist häufig jedoch gar nicht so einfach. So gilt es bei­spiels­wei­se darauf zu achten, dass nicht jedes Mal der Typ eines Objekts vor der ei­gent­li­chen Da­ten­ver­ar­bei­tung abgefragt werden muss, da ein solches Szenario weder effizient noch per­for­mant wäre. Ins­be­son­de­re, wenn viele primitive Objekte auf zu­sam­men­ge­setz­te Objekte treffen, empfiehlt sich daher der Einsatz des so­ge­nann­ten Composite Design Patterns (dt. Kom­po­si­tum-Ent­wurfs­mus­ter). Der Soft­ware­de­sign-Ansatz er­mög­licht es Clients, in­di­vi­du­el­le und zu­sam­men­ge­setz­te Objekte ein­heit­lich zu behandeln, indem ihre Un­ter­schie­de vor dem Client verborgen werden.

Was ist das Composite Pattern (Composite-Muster)?

Das Composite Design Pattern (dt. Kom­po­si­tum-Ent­wurfs­mus­ter), kurz Composite Pattern, ist eines der 23 GoF-Design-Pattern für die Software-Ent­wick­lung, die 1994 von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (gemeinsam auch als „Gang of Four“ bekannt) ver­öf­fent­licht wurden. Wie bei­spiels­wei­se auch das Facade Pattern und das Decorator Pattern zählt es dabei zu den Struk­tur­mus­tern, deren grund­le­gen­de Funktion es ist, Objekte und Klassen zu größeren Struk­tu­ren zu­sam­men­zu­fas­sen.

Welche Probleme löst das Composite-Ent­wurfs­mus­ter?

Der grund­sätz­li­che Zweck des Composite Design Patterns ist – wie bei allen GoF-Mustern – der best­mög­li­che Umgang mit wie­der­keh­ren­den Design-Problemen in der ob­jekt­ori­en­tier­ten Ent­wick­lung. Das ge­wünsch­te Ergebnis ist eine möglichst flexible Software, die sich durch leicht im­ple­men­tier­ba­re, testbare, aus­tausch­ba­re und wie­der­ver­wend­ba­re Objekte aus­zeich­net. Das Composite Pattern be­schreibt zu diesem Zweck einen Weg, wie sich einzelne Objekte und zu­sam­men­ge­setz­te Objekte auf die gleiche Weise behandeln lassen. Auf diese Weise lässt sich eine Ob­jekt­struk­tur schaffen, die leicht ver­ständ­lich ist und maximal ef­fi­zi­en­te Client-Zugriffe er­mög­licht. Zudem wird auch die Feh­ler­an­fäl­lig­keit des Codes minimiert.

Composite Design Pattern: Grafische Dar­stel­lung (UML)

Um die an­ge­spro­che­nen, ef­fi­zi­en­ten Teil-Ganzes-Hier­ar­chien um­zu­set­zen, sieht das Composite-Muster die Im­ple­men­tie­rung eines ein­heit­li­chen Kom­po­nen­ten-In­ter­faces für simple Teil-Objekte, die auch als Leaf-Objekte (engl. für „Blatt“) be­zeich­net werden, und zu­sam­men­ge­setz­te Composite-Objekte (engl. für „zu­sam­men­ge­setzt“) vor. Einzelne Leaf-Objekte binden dieses Interface direkt ein, Composite-Objekte geben konkrete Client-An­for­de­run­gen an die Schnitt­stel­le au­to­ma­tisch an ihre un­ter­ge­ord­ne­ten Kom­po­nen­ten weiter. Für den Client spielt es dabei keine Rolle, um welchen Typ von Objekt (Teil oder Ganzes) es sich handelt, da er lediglich das Interface an­spre­chen muss.

Das nach­fol­gen­de Klas­sen­dia­gramm in der Mo­del­lie­rungs­spra­che UML macht die Zu­sam­men­hän­ge und Hier­ar­chien in einer Software auf Composite-Pattern-Basis deut­li­cher.

Stärken und Schwächen des Composite-Ent­wurfs­mus­ters

Das Composite Pattern zählt in der Soft­ware­ent­wick­lung zu einer festen Größe. Ins­be­son­de­re Projekte mit stark ver­schach­tel­ten Struk­tu­ren pro­fi­tie­ren von dem prak­ti­schen Ansatz für die Or­ga­ni­sa­ti­on von Objekten: Ob pri­mi­ti­ves Objekt oder zu­sam­men­ge­setz­tes Objekt, ob mit simplen oder mit komplexen Ab­hän­gig­kei­ten – die Tiefe und Breite der Ver­schach­te­lung spielt beim Composite Design Pattern grund­sätz­lich keine Rolle. Die Un­ter­schie­de zwischen den Ob­jekt­ar­ten können vom Client gänzlich ignoriert werden, sodass keine separaten Funk­tio­nen für den Zugriff er­for­der­lich sind. Das bringt den Vorteil mit sich, dass der Client-Code einfach und schlank bleibt.

Eine weitere Stärke des Kom­po­si­tum-Ent­wurfs­mus­ters ist die Fle­xi­bi­li­tät und einfache Er­wei­ter­bar­keit, die das Pattern einer Software verleiht: Das uni­ver­sel­le Kom­po­nen­ten-Interface er­mög­licht es, neue Leaf- und Composite-Objekte ohne Code-Än­de­run­gen ein­zu­bin­den – ob auf Client-Seiten oder bei bereits be­stehen­den Ob­jekt­struk­tu­ren.

Auch wenn das Composite Pattern und seine ein­heit­li­che Schnitt­stel­le eine Reihe von Vorteilen bieten, ist diese Mus­ter­lö­sung nicht frei von Schwächen. Ins­be­son­de­re das Interface kann Ent­wick­lern eine Menge Kopf­zer­bre­chen bereiten. Bereits die Im­ple­men­tie­rung stellt die Ver­ant­wort­li­chen vor große Her­aus­for­de­run­gen, da bei­spiels­wei­se zu ent­schei­den ist, welche Ope­ra­tio­nen konkret in der Schnitt­stel­le und welche in den Composite-Klassen definiert werden sollen. Auch eine nach­träg­li­che Anpassung der Composite-Ei­gen­schaf­ten (zum Beispiel die Ein­schrän­kung, welche Child-Elemente erlaubt sind) erweist sich meist als kom­pli­ziert und nur schwer zu rea­li­sie­ren.

Vorteile Nachteile
Liefert alles, um stark verĀ­schachĀ­telĀ­te ObĀ­jektĀ­strukĀ­tuĀ­ren darĀ­zuĀ­stelĀ­len ImĀ­pleĀ­menĀ­tieĀ­rung des KomĀ­poĀ­nenĀ­ten-InĀ­terĀ­faces ist sehr herĀ­ausĀ­forĀ­dernd
Schlanker, leicht ver­ständ­li­cher Pro­gramm­code Nach­träg­li­che Anpassung der Composite-Ei­gen­schaf­ten sind kom­pli­ziert und zur schwer um­zu­set­zen.
Gute ErĀ­weiĀ­terĀ­barĀ­keit

An­wen­dungs­sze­na­ri­en für das Composite Pattern

Der Einsatz des Composite-Musters zahlt sich überall dort aus, wo Ope­ra­tio­nen auf dy­na­mi­sche Da­ten­struk­tu­ren aus­ge­führt werden sollen, deren Hier­ar­chie von komplexer Breite und/oder Tiefe ist. Man spricht in diesem Fall auch von einer binären Baum­struk­tur, die für die ver­schie­dens­ten Soft­ware­sze­na­ri­os in­ter­es­sant ist und häufig zur Ver­wen­dung kommt. Typische Beispiele sind:

Da­tei­sys­te­me: Zu den wich­tigs­ten Kom­po­nen­ten von Geräte-Software gehören Da­tei­sys­te­me. Diese lassen sich optimal mit dem Composite Pattern abbilden: Einzelne Dateien als Leaf-Objekte und Ordner – die ih­rer­seits selbst Dateien oder weitere Ordner enthalten können – als Composite-Objekte.

Software-Menüs: Auch Pro­gramm­me­nüs stellen einen typischen Use Case für eine binäre Baum­struk­tur nach dem Composite Design Pattern dar. In der Me­nü­leis­te befinden sich ein oder mehrere Wur­ze­lein­trä­ge (Composite-Objekte) wie „Datei“. Diese gewähren Zugriff auf ver­schie­de­ne Me­nü­punk­te, die wahlweise direkt klickbar sind (Leaf) oder weitere un­ter­glie­der­te Menüs (Composite) enthalten.

Grafische Be­nut­zer­ober­flä­chen (GUIs): Auch bei der Ge­stal­tung von gra­fi­schen Be­nut­zer­ober­flä­chen können Baum­struk­tu­ren und das Composite-Ent­wurfs­mus­ter eine wichtige Rolle spielen. Abseits von simplen Leaf-Elementen wie Buttons, Text­fel­dern oder Check­bo­xen sorgen zu­sam­men­fas­sen­de Composite-Behälter wie Frames oder Panels für eine klare Struktur und mehr Über­sicht­lich­keit.

Code-Beispiel: Composite Pattern

In kaum einer Pro­gram­mier­spra­che ist der Composite-Pattern-Ansatz so fest etabliert wie in Java. Unter anderem bildet das Muster bei­spiels­wei­se auch die Basis des Abstract Window Toolkits (AWT), eines prak­ti­schen und beliebten API mit rund 50 ein­satz­fer­ti­gen Java-Klassen für die Ent­wick­lung platt­form­über­grei­fen­der Java-Ober­flä­chen. Auch im nach­fol­gen­den Code-Beispiel, das sich am Beitrag „Composite Design Pattern in Java“ auf baeldung.com ori­en­tiert, haben wir uns daher für die populäre Pro­gram­mier­spra­che ent­schie­den.

Im Praxis-Beispiel soll die hier­ar­chi­sche Struktur von Ab­tei­lun­gen (de­part­ments) in einem Un­ter­neh­men (company) dar­ge­stellt werden. Zunächst wird hierfür das Kom­po­nen­ten-InterfaceDe­part­ment“ definiert:

public interface Department {
	void printDepartmentName();
}

An­schlie­ßend werden die zwei einfachen Leaf-KlassenFi­nan­cial­De­part­ment“ (für die Fi­nanz­ab­tei­lung) und „Sa­les­De­part­ment“ (für die Ver­kaufs­ab­tei­lung) definiert. Beide im­ple­men­tie­ren die Methode print­De­part­ment­Na­me() von der Kom­po­nen­ten-Schnitt­stel­le, enthalten aber ansonsten keine weiteren De­part­ment-Objekte.

public class FinancialDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}
public class SalesDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}

Eine zur Hier­ar­chie passende Composite-Klasse wird schließ­lich mit „Head­De­part­ment“ definiert. Diese setzt sich aus mehreren De­part­ment-Kom­po­nen­ten zusammen und enthält zu­sätz­lich zu der print­De­part­ment­Na­me()-Methode auch Methoden, um weitere Elemente hin­zu­zu­fü­gen (addDe­part­ment) oder vor­han­de­ne Elemente zu entfernen (re­mo­ve­De­part­ment):

public class HeadDepartment implements Department {
	private Integer id;
	private String name;
	private List<department> childDepartments;</department>
	public HeadDepartment(Integer id, String name) {
		this.id = id;
		this.name = name;
		this.childDepartments = new ArrayList<>();
	}
	public void printDepartmentName() {
		childDepartments.forEach(Department::printDepartmentName);
	}
	public void addDepartment(Department department) {
		childDepartments.add(department);
	}
	public void removeDepartment(Department department) {
		childDepartments.remove(department);
	}
}
Zum Hauptmenü