Dynamic data struc­tures require a clear and concise hi­er­ar­chi­cal structure. Putting these struc­tures into practice is often not that easy. Care must be taken that the type of object is not queried every time before the actual data is processed because this would not be efficient. Using a Composite Design Pattern is rec­om­mend­ed when many primitive objects meet composite objects. This software design approach lets clients treat in­di­vid­ual and compound objects con­sis­tent­ly by hiding their dif­fer­ences from the client.

What is a Composite Pattern?

The Composite Design Pattern (Composite Pattern in short) is one of 23 GoF design patterns for software de­vel­op­ment, published by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (col­lec­tive­ly referred to as the “Gang of Four”) in 1994. Like the Facade Pattern and the Decorator Pattern, it is a design pattern which compounds objects and classes into larger struc­tures.

The Composite Pattern follows the basic idea of rep­re­sent­ing simple objects and their con­tain­ers or com­po­si­tions (i.e. com­po­si­tions of objects) in an abstract class so they can be treated uniformly. This type of structure is referred to as part-whole hierarchy, whereby an object is either only part of a whole or the whole that consists of in­di­vid­ual parts.

Which problems can be solved using a Composite Pattern UML?

The basic purpose of the Composite Design Pattern is – as with all GoF patterns – the best possible handling of recurring design problems in object-oriented de­vel­op­ment. The desired end result is flexible software, char­ac­ter­ized by easily im­ple­mentable, testable, ex­change­able, and reusable objects. To this end, the Composite Pattern describes a way in which in­di­vid­ual objects and composite objects can be treated in the same way. In this way, an object structure can be created that is easy to un­der­stand and enables highly efficient client access. Further, the code's sus­cep­ti­bil­i­ty to errors is minimized.

Composite Design Pattern: graphical notation (UML)

To implement part-whole hi­er­ar­chies, the Composite Pattern provides for the im­ple­men­ta­tion of a uniform component interface for simple part objects, also referred to as leaf objects, and composite objects. In­di­vid­ual leaf objects directly integrate this interface; composite objects au­to­mat­i­cal­ly forward specific client re­quire­ments to the interface and their sub­or­di­nate com­po­nents. For the client, it doesn’t matter what type an object is (part or whole) since it only addresses the interface.

The following class diagram using the graphical notation UML vi­su­al­izes con­nec­tions and hi­er­ar­chies of a software Composite Pattern more clearly.

Strengths and weak­ness­es of the Composite Pattern

The Composite Pattern is a constant in software de­vel­op­ment. Projects with heavily nested struc­tures tend to benefit from practical approach that are objects: whether that’s a primitive object or a composite object, whether it has simple or complex de­pen­den­cies. The depth and width of the nesting doesn’t matter in the Composite Design Pattern. The dif­fer­ences between the object types can be ignored by the client so that no separate functions are required for a query. This has the advantage that the client code is simple and lean.

Another advantage of the Composite Pattern is the flex­i­bil­i­ty and simple ex­tendibil­i­ty that the pattern gives a software. The universal component interface allows the in­te­gra­tion of new leaf and composite objects without changes to the code – whether on the client side or existing object struc­tures.

But despite all its ad­van­tages, the Composite Pattern and its uniform interface do have some weak­ness­es. The interface can be a pain for de­vel­op­ers. Im­ple­men­ta­tion comes with several major chal­lenges. For example, it should be decided upfront which op­er­a­tions are to be defined in the interface and which are to be defined in the composite classes. Sub­se­quent ad­just­ment of the composite prop­er­ties (for example, the re­strict­ing child elements) is often com­pli­cat­ed and difficult to implement.

Ad­van­tages Dis­ad­van­tages
Provides every­thing to display heavily nested object struc­tures Im­ple­men­ta­tion of component in­ter­faces is very chal­leng­ing
Lean, easy-to-un­der­stand program code Sub­se­quent ad­just­ment of composite features is difficult and cum­ber­some to realize
Great ex­pand­abil­i­ty

Ap­pli­ca­tion areas for the Composite Pattern

Using a Composite Pattern pays off wherever op­er­a­tions need to be carried out on dynamic data struc­tures with complex hi­er­ar­chies (in terms of depth and width). This is also referred to as a binary tree structure, which is in­ter­est­ing for a wide variety of software scenarios and is often used. Typical examples are:

Data systems: Data systems are among the most important com­po­nents of device software. These can be easily displayed using a Composite Pattern. Single files are displayed as leaf objects or folders, which can contain their own data or sub-folders.

Software menus: Program menus represent typical use cases for a binary tree structure according to the Composite Design Pattern. Program menus represent a typical use case for a binary tree structure according to the composite design pattern. The menu bar contains one or more root entries (composite objects) such as “File”. These provide access to various menu items that can either be clicked directly (leaf) or contain further sub-menus (composite).

Graphical interface (GUIs): Tree struc­tures and the Composite Pattern also play an important role in the design of graphical user in­ter­faces. Aside from simple leaf elements such as buttons, text fields, or check­box­es, composite con­tain­ers such as frames or panels ensure a clear structure and greater clarity.

Code example: Composite Pattern

The Composite Pattern is probably best es­tab­lished in the Java pro­gram­ming language. The pattern forms the basis of the Abstract Window Toolkit (AWT), among other things. AWT is a practical and popular API with around 50 ready-to-use Java classes for the de­vel­op­ment of cross-platform Java in­ter­faces. In the code example that follows – taken from the blog entry “Composite Design Pattern in Java” on baeldung.com – we’ve focused on the popular pro­gram­ming language.

In this practical example, the hi­er­ar­chi­cal structure of company de­part­ments is shown. As a first step, the component interfaceDe­part­ment” is defined:

public interface Department {
	void printDepartmentName();
}

Sub­se­quent­ly, two simple leaf classesFi­nan­cialDe­part­ment” (for the finance de­part­ment) and “Sales­De­part­ment” (for the sales de­part­ment) are defined. Both implement the print­De­part­ment­Name() method from the component interface, but don’t contain any further De­part­ment objects.

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
}

A composite class that fits the hierarchy is defined as “Head­De­part­ment”. This consists of several de­part­ment com­po­nents and, in addition to the print­De­part­ment­Name() method contains methods for adding further elements (ad­dDe­part­ment) or removing existing elements (removeDe­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);
	}
}
Go to Main Menu