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 baseComponent
is 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 otherComponent
s.Composite
– It implements the operations in the baseComponent
and contains references to otherComponents
.Client
– manipulates objects in the composition through the baseComponent
interface.
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
Component
andContainer
classes in thejava.awt
package. AContainer
extends the abstract superclass ofComponent
. AContainer
can contain otherComponent
s. We canadd()
,remove()
and otherwise manipulateComponent
s. This gives rise to a tree structure ofComponent
s. - The JSF (JavaServer Faces)
javax.faces.component.UIComponent
can contain otherUIComponent
s. 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 MechanisedFightingRobot
s.
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 MechanisedFightingUnit
s would contain other MechanisedFightingUnit
s. A MechanisedFightingVehicle
would contain MechanisedFightingRobot
s. This containment relationship can be modelled as a common abstract base class containing a collection of MechanisedFightingUnit
s 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
Component
class with theadd()
,remove()
,getComponent()
, etc. methods to make it aComposite
class. - 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.