The Composite Pattern

The Composite Pattern

Introduction

The composite pattern is one of the classical 23 patterns described by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides in their high-impact book Design Patterns: Elements of Reusable Object-Oriented Software.

The composite pattern is catalogued as a structural pattern. It defines a way to deal with objects which can contain other objects of the same type. The pattern in itself is relatively simple. You have to decide though what is more important to you: transparency or safety. Dependent on your priority your code will have to be structured slightly differently. We will deal with this topic when discussing the structure of the composite pattern.

Gamma et al. also known as the Gang of Four, define the composite pattern as follows:

[Its intent is to] compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Gamma et al., 2003, p. 163

Relation with other patterns

The composite pattern is not very similar to any other pattern. But its conjunction with other patterns is common:

  • Iterators are generally used for traversing composites.
  • Flyweights can be used to reduce the memory footprint.
  • You can use the hierarchy of objects to create a Chain of Responsibility.
  • You can use Builders to create objects and Factories to create and pass them to the client.

Structure of the composite pattern

The composite pattern defines four roles:

  • Component – all objects are components i.e. implement the interface or extend the abstract class which fulfills this role.
  • Composite – composites are special components which can contain further components, be they other composites or leaves.
  • Leaf – leaves are simple components which cannot contain further objects of the type component.
  • Client – the client is the one who interacts with the components. Dependent on the decision transparency vs. safety he will have to write more code or less.

As mentioned above the focus on transparency or safety can lead to slightly different structures of the pattern.

Transparency-first approach

The Gang of Four favor in their example the transparency approach. Let’s have a look at their proposition (adapted from Gamma et al., 2003, p. 163):

The client doesn’t have to be aware whether he deals with a leaf or a composite.

As you can easily see, the Client only interacts with the Component interface. As long as Leaves and Composites are instantiated for him, he doesn’t even have to know the Component’s subclasses. You could use factories or dependency injection for this. So, in this case differences are transparent to him. You have to decide though, what should happen when he tries to add a Component to a Leaf. There are several possibilities:

  • You could throw a runtime exception and inform the client, that his action is illegal. This is not very nice though, because you promised him uniformity. You could also throw a checked exception and make the issue visible, but this isn’t really better.
  • A second possibility is to provide a no-op implementation in the leaves and just do nothing. Alternatively, you can offer an empty default implementation in the interface. Transparency is preserved this way.
  • A third possibility is to offer transparency and to make the differences visible. For this you could define a method boolean isComposable() which is answered true by composites and false by leaves. This way you would use polymorphism to solve the issue. But you are still left with the decision whether to throw an exception or to just do nothing in case the client does not use the isComposable() method.
isComposable() offers the possibility to ask an object about his nature.

Safety-first approach

A second implementation variant favors type safety. Let’s have a look at it:

In this version only composites can contain other components.

With this design, the client can still doSomething() uniformly, but he has to know about composites and leaves. If he wants to treat components uniformly, he will have to do a cast at some point:

Component composite = new Composite();
Component leaf = new Leaf();

((Composite) composite).addComponent(leaf);
composite.doSomething(); // do something to all

You could make Composite abstract or turn it into an interface (which implements Component). You could even interpret leaf and composite as roles like in the following diagram (cf. Steimann 2000, p. 173 et seq.) :

But you won’t get rid of the cast, which defeats to a certain extent the intention of this pattern. Gamma et al. propose as a solution to declare a method GetComposite() in Component which returns per default null. Composites override the method and return themselves. This is also not very elegant, since the client now has to deal with null as a potential return value. You could use the Null Object Pattern to alleviate this problem.

Transparency vs. safety

But in the end no fully elegant solution exists and you have to decide in your concrete implementation what is it that you favor more, or what is it that suits the problem at hand better: transparency or safety.

The Gang of Four write about the transparency vs. safety problem:

Defining child management in the Composite class gives you safety, because any attempt to add or remove objects from leaves will be caught at compile-time […]. But you lose transparency, because leaves and composites have different interfaces. We have emphasized transparency over safety in this pattern. If you opt for safety, then at times you may lose type information and have to convert a component into a composite.

Gamma et al., 2004, p. 167 et seq.

I tend to agree that transparency is the great benefit of the composite pattern, which is why I chose an example following the transparency-first structure which we will discuss next.

Examples for the composite pattern

Let’s consider a file system where we have Files and Folders. Both are considered StorageUnits. I decided to define StorageUnit as an abstract class instead of an interface because like this I can already define the state shared by all StorageUnits, in this case the name.

StorageUnit in the role of the Component

public abstract class StorageUnit {

	private String name;

	public StorageUnit(String name) {
		this.name = name;
	}

	public String getName() {return name;}
	public abstract void addElement(StorageUnit element);
	public abstract void removeElement(StorageUnit element);
	public abstract int getNumberOfSubelements();
	protected abstract void remove(StorageUnit parent);
	
	// manage permissions, properties, actions like moving, renaming etc.
}

As you can see, StorageUnits have a name and that’s about it. I omitted creation date, modification date, size etc. for simplicity. You can add sub-elements to a storage unit. These can be either folders or files, you can remove them, and you can query the total number of sub-elements. The remove(StorageUnit parent) is protected and only intended for internal use. Clients can remove a single element trough the removeElement(StorageUnit element) method. Remove passes the word to the sub-elements by calling their respective remove methods and passing itself through this as a parameter. I use the parent solely for printing out its name, but other use cases are possible. Now, you could ask, why we need to pass the word and not simply remove the file or folder from the list. Firstly, I chose this approach because it better fits the demonstration – you can see like this how StorageUnits are removed one by one. Secondly, there might exist more pragmatic motives like logging the removal of each file or doing some pre-removal checks which only by the file itself can perform.

Files and folders as leaves and composites

Let’s have a look at the implementation of folders and files now:

public class Folder extends StorageUnit {

	private final CopyOnWriteArrayList< StorageUnit > subelements;
	
	public Folder(String name) {
		super(name);
		this.subelements = new CopyOnWriteArrayList<>();
	}
	
	public void removeElement(StorageUnit element) {
		element.remove(this);
		subelements.remove(element);
	}

	public void addElement(StorageUnit element) {
		if (subelements.addIfAbsent(element)) {
			System.out.println("Add " + element.getName() + 
				" to " + this.getName());
		}
	}

	/**
	 * Remove elements recursively. No need to worry about 
	 * concurrent modifications here since we use 
	 * {@link CopyOnWriteArrayList} for simplicity.
	 * Might not be the best solution for every situation.
	 */
	protected void remove(StorageUnit parent) {
		for (StorageUnit element : subelements) {
			element.remove(this);
		}
		System.out.println("Remove Folder " + getName() + 
			" from " + parent.getName());
	}

	public int getNumberOfSubelements() {
		int tmp = subelements.size();
		for (StorageUnit element : subelements) {
			tmp += element.getNumberOfSubelements();
		}
		return tmp;
	}
}

I used a CopyOnWriteArrayList because this makes the removing while iterating easy, however this might not be the best solution in all cases.

public class File extends StorageUnit {

	public File(String name) {
		super(name);
	}

	@Override
	public void addElement(StorageUnit element) { // no-op }

	@Override
	public void removeElement(StorageUnit element) { // no-op }

	@Override
	protected void remove(StorageUnit parent) {
		System.out.println("Remove File " + getName() + 
				" from " + parent.getName());
	}
	
	@Override
	public int getNumberOfSubelements() { return 0; }
}

As you can see, files don’t do much. They have a name inherited from their parent-class, they have zero sub-elements and they print out a message when removed.

Verification

And now let’s have a look at the usage of the composite. For this we create several files and folders and create a hierarchical structure out of them.

StorageUnit file1 = new File("File-1");
StorageUnit file2 = new File("File-2");
StorageUnit file3 = new File("File-3");
StorageUnit file4 = new File("File-4");

StorageUnit folder1 = new Folder("Folder-1");
StorageUnit folder2 = new Folder("Folder-2");
StorageUnit folder3 = new Folder("Folder-3");
StorageUnit folder4 = new Folder("Folder-4");

addElementsToFolders(folder4, file4, file3);
addElementsToFolders(folder3, file2);
addElementsToFolders(folder2, folder3, file1);
addElementsToFolders(folder1, folder2, folder4);

With addElementsToFolders implemented as follows:

private void addElementsToFolders(StorageUnit parent, StorageUnit... elements) {
	System.out.println("--------------------------------");
	System.out.println("--- Add elements to " + parent.getName() + 
		" ---");
	System.out.println("--------------------------------");

	for (StorageUnit element: elements) {
		parent.addElement(element);
	}
	System.out.println();
}

And the output to this:

--------------------------------
--- Add elements to Folder-4 ---
--------------------------------
Add File-4 to Folder-4
Add File-3 to Folder-4

--------------------------------
--- Add elements to Folder-3 ---
--------------------------------
Add File-2 to Folder-3

--------------------------------
--- Add elements to Folder-2 ---
--------------------------------
Add Folder-3 to Folder-2
Add File-1 to Folder-2

--------------------------------
--- Add elements to Folder-1 ---
--------------------------------
Add Folder-2 to Folder-1
Add Folder-4 to Folder-1

The next step is the demonstration of recursive removal of elements. For this, I am going to remove Folder-2 which contains File-1 and Folder-3, which in turn contains File-2.

System.out.println("--------------------------------------");
System.out.println("Number of elements in Folder-1: " + 
		folder1.getNumberOfSubelements());
System.out.println();
		
System.out.println("----------- Remove Folder-2 ----------");
System.out.println("--------------------------------------");
folder1.removeElement(folder2);
System.out.println("--------------------------------------");
System.out.println();
		
System.out.println("Number of elements in Folder-1: " + 
		folder1.getNumberOfSubelements());
System.out.println("--------------------------------------");

The output is:

--------------------------------------
Number of elements in Folder-1: 7

----------- Remove Folder-2 ----------
--------------------------------------
Remove File File-2 from Folder-3
Remove Folder Folder-3 from Folder-2
Remove File File-1 from Folder-2
Remove Folder Folder-2 from Folder-1
--------------------------------------

Number of elements in Folder-1: 3
--------------------------------------

As we can see the structure was traversed and the StorageUnits were removed one by one.

And now, let’s have a look at some real-life usage of the pattern.

Real-life examples of the composite pattern

The use case of the composite pattern is common, every now and then you have objects which can be composed from other objects of the same type. GUI libraries often make use of this pattern. AWT and Swing are no exception. The class Container extends the class Component. A container is composed of various other components. You can add them through java.awt.Container#addComponent(Component) and remove them by calling the corresponding method java.awt.Container#remove(Component).

Conclusion

In this rather lengthy article, we had a look at the Composite Pattern. We discussed the two main foci of the pattern: transparency vs. safety and demonstrated the patterns usage through a simple example.


References

Gamma, E., Helm, R., Johnson, R. & Vlissides, J. (2003). Design Patterns. Delhi, India: Pearson Education.

Steimann, F. (2000). Formale Modellierung mit Rollen. Retrieved on 18.03.2020 from https://www.fernuni-hagen.de/ps/pubs/HabilitationsschriftSteimann.pdf.


You might also like the following articles:

Petre Sora

Petre Soras Interessen sind vielfältig und befinden sich an der Schnittstelle zwischen Mensch und Informationstechnologie. Als studierter Psychologe und Software Engineer war er sechs Jahre als Java-Entwickler in mehreren Unternehmen tätig. Mit der Gründung der Rezensionsplattform nososo hat er sich entschieden eigene Wege zu gehen.

Schreibe einen Kommentar