Dynamic data struc­tures require a clear and concise hier­arch­ic­al 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 re­com­men­ded when many primitive objects meet composite objects. This software design approach lets clients treat in­di­vidu­al and compound objects con­sist­ently 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­lect­ively 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­res­ent­ing simple objects and their con­tain­ers or com­pos­i­tions (i.e. com­pos­i­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­vidu­al 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­ised by easily im­ple­ment­able, testable, ex­change­able, and reusable objects. To this end, the Composite Pattern describes a way in which in­di­vidu­al 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­cept­ib­il­ity to errors is minimised.

Composite Design Pattern: graphical notation (UML)

To implement part-whole hier­arch­ies, the Composite Pattern provides for the im­ple­ment­a­tion of a uniform component interface for simple part objects, also referred to as leaf objects, and composite objects. In­di­vidu­al leaf objects directly integrate this interface; composite objects auto­mat­ic­ally forward specific client re­quire­ments to the interface and their sub­or­din­ate com­pon­ents. 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 visu­al­ises con­nec­tions and hier­arch­ies of a software Composite Pattern more clearly.

Strengths and weak­nesses 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­pend­en­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­ib­il­ity and simple ex­tend­ib­il­ity that the pattern gives a software. The universal component interface allows the in­teg­ra­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­vant­ages, the Composite Pattern and its uniform interface do have some weak­nesses. The interface can be a pain for de­velopers. Im­ple­ment­a­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­sequent ad­just­ment of the composite prop­er­ties (for example, the re­strict­ing child elements) is often com­plic­ated and difficult to implement.

Ad­vant­ages Dis­ad­vant­ages
Provides everything to display heavily nested object struc­tures Im­ple­ment­a­tion of component in­ter­faces is very chal­len­ging
Lean, easy-to-un­der­stand program code Sub­sequent ad­just­ment of composite features is difficult and cum­ber­some to realise
Great ex­pand­ab­il­ity

Ap­plic­a­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 hier­arch­ies (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­pon­ents 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­boxes, 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 hier­arch­ic­al 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­sequently, two simple leaf classesFin­an­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 ‘He­ad­De­part­ment’. This consists of several de­part­ment com­pon­ents 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 (removeDepart­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