This week we’ll continue our exploration of design patterns and look at a commonly used structural pattern, the Composite design pattern.
The Composite Pattern
The Gang of Four book defines the Composite pattern as follows:
“Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.”
We can apply the Composite pattern when there is a part-whole hierarchy of objects, and a client needs to deal with objects uniformly regardless of the fact that an object might be a leaf (simple object) or a branch (composite object).
The key to the Composite pattern is an abstract class that represents both the primitive objects and their containers.
This simplifies the client code. The client treats simple objects (the leaves) and composite objects (the branches) in the same way. The client doesn’t know or care whether they’re dealing with one or the other. It also makes it very easy to add new kinds of components or containers. They will automatically work with the existing code.
For example, think about working with a file system. Files are simple objects. Directories (folders) are compositions of files and other directories. Both files and directories have names, sizes, attributes, etc. It would be very convenient to treat both files and directories in a uniform way. We could define a FileResource abstract class that would model the similarities between files and directories. The standard java.io.File class springs to mind for that.
Another common example comes from the manufacturing industry. Let’s say we were building engines. Engines are built from individual parts and various assemblies. An assembly is a group of parts that is pre-built (i.e., assembled). Assemblies are built from individual parts and maybe other assemblies. The Composite pattern allows us to treat both assemblies and individual parts as if they were the same type. We can then programmatically process them in the same way, which simplifies our code.
Structure
The Composite pattern consists of the following participants.
- Base
Component– the baseComponentis the interface for all objects in the composition. It can be an interface or an abstract class with the methods common to all the objects. Leaf– Defines the behaviour for the elements in the composition. It is the building block for the composition and implements base component. It doesn’t contain references to otherComponents.Composite– It implements the operations in the baseComponentand contains references to otherComponents.Client– manipulates objects in the composition through the baseComponentinterface.
Composite in the Java APIs
The Composite pattern is recognisable by structural methods taking an instance of the same interface/abstract type and placing it in a tree structure. Some examples from the standard Java APIs:
- The
ComponentandContainerclasses in thejava.awtpackage. AContainerextends the abstract superclass ofComponent. AContainercan contain otherComponents. We canadd(),remove()and otherwise manipulateComponents. This gives rise to a tree structure ofComponents. - The JSF (JavaServer Faces)
javax.faces.component.UIComponentcan contain otherUIComponents. This is another tree structure.
Example
In our first person shooter game, we’ve been coming up with all sorts of great ideas to enhance game play, and maybe even spin off some other games while we’re about it.
Our next brilliant idea is creating mechanised fighting units, like self-driving armoured vehicles and autonomous fighting robots. We’d obviously want to command them and aim them towards specific targets. We’d like to command the robots in the same way as we command the tanks. We’d also like to command the robots in the same way if they were moving under their own power or riding in an self-driving armoured fighting vehicle.
We could model these robots and vehicles as MechanisedFightingVehicle and MechanisedFightingRobots.
We know that all fighting units should fight, so in a time-honoured practice we will create an interface to contain the common fight() method. To make it more generic to our game, we will just call it a FightingUnit:
public interface FightingUnit {
public void fight();
}
Both the MechanisedFightingVehicle and MechanisedFightingRobot classes will implement this interface. We see that some MechanisedFightingUnits would contain other MechanisedFightingUnits. A MechanisedFightingVehicle would contain MechanisedFightingRobots. This containment relationship can be modelled as a common abstract base class containing a collection of MechanisedFightingUnits with methods to add and remove units.
The following code is the base class for the containment relationship.
public abstract class MechanisedFightingUnit implements FightingUnit{
private String name;
public MechanisedFightingUnit(String name) {
this.name = name;
}
public String getName() {
return name;
}
// abstract methods to be implemented appropriately by subclasses
public abstract void fight();
public abstract void add(MechanisedFightingUnit unit);
public abstract void remove(MechanisedFightingUnit unit);
public abstract MechanisedFightingUnit[] getUnits();
} // end of class
We will leave the actual collection implementations to the container classes which could be MechanisedFightingVehicle, MechanisedFightingVehicleTransporter, MechanisedFightingBattleship, etc.
import java.util.*;
public class MechanisedFightingVehicle extends MechanisedFightingUnit {
private List units;
public MechanisedFightingVehicle(String name) {
super (name);
units = new ArrayList<>();
}
public void add(MechanisedFightingUnit unit) {
units.add(unit);
}
public void remove(MechanisedFightingUnit unit) {
units.remove(unit);
}
public MechanisedFightingUnit[] getUnits() {
return units.toArray(new MechanisedFightingUnit[units.size()]);
}
public void fight() {
// appropriate code for a self-driving vehicle to fight
System.out.println("Vehicle " + getName() + " fighting!");
// as well as a call for all contained FightingUnits to fight
for (MechanisedFightingUnit unit : getUnits())
unit.fight();
}
} // end of class
The robots would not contain any other fighting units, so the collection code – the add(), remove() and getUnits() methods – would be implemented as empty (or close to empty) methods:
public class MechanisedFightingRobot extends MechanisedFightingUnit {
public MechanisedFightingRobot(String name) {
super(name);
}
// container methods implemented as empty/near-empty methods
public void add(MechanisedFightingUnit unit) {}
public void remove(MechanisedFightingUnit unit) {}
public MechanisedFightingUnit[] getUnits() {
return new MechanisedFightingUnit[0];
}
public void fight() {
// appropriate code for a single robot to fight
System.out.println("Robot " + getName() + " is fighting!");
}
} // end of class
These classes would be used in client code as follows:
MechanisedFightingUnit t800 = new MechanisedFightingRobot("T-800");
MechanisedFightingUnit t900 = new MechanisedFightingRobot("T-900");
MechanisedFightingUnit t1000 = new MechanisedFightingRobot("T-1000");
MechanisedFightingUnit botvee = new MechanisedFightingVehicle("BotVee");
// T-900 and T-1000 ride in a vehicle
botvee.add(t900);
botvee.add(t1000);
t800.fight(); // fights by itself
botvee.fight(); // fights together with the contained units
Comparison with Other Design Patterns
- Composite and Decorator have similar structures. They both rely on recursive composition to organise an open-ended number of objects.
- Decorator add responsibilities to objects without subclassing. Composite is focused on the representation of the tree structure, and not on embellishing the objects in the tree.
- A Decorator can be used to decorate the base
Componentclass with theadd(),remove(),getComponent(), etc. methods to make it aCompositeclass. - An Iterator can be used to traverse through the composite tree structure.
What’s Next?
In the weeks ahead, we’ll continue examining some of the more useful design patterns. Stay tuned! And don’t forget to post a comment and let me know what you think.