The Composite Pattern allows you to compose objects into tree structures to represent
part-whole hirarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
The Composite Pattern allows us to build structures of objects in the form of trees that contain both compositions of objects and individual objects as nodes. A composite contains components (so a component is any object in a composite structure), which come in two flavors:
composites and leafe elements. This recursive structure contains composites that hold a set of children.
Those children may be other composites or leaf elements.
Using a composite structure, we can apply the same operations over both composites and individual objects.
In other words, in most cases we can ignore the differences between compositions of objects and individual objects.
This allows clients to treat composites and individual objects uniformly.
The Iterator Pattern is commonly used with the Composite Pattern to iterate over its components.
A Component can implement default behavior for its Leaf and Composite subclasses.
If a base class method is different in the subclasses a valid default implementation is to throw an exception.
There are many design tradeoffs in implementing Composite. You need to balance transparency and safety with your needs.
Note that depending on the perspective, not all methods make sense for both subclasses of the Component interface.
For example, the child node management methods add(), remove() and getChild() seem incorret when applied on a Leaf node. However, a leaf node can be seen as a node with zero children.
The Composite Pattern takes the Single Responsibility Principle and trades it for transparency.
By allowing the Component interface to contain the child management operations and the leaf operations,
a client can treat both composites and leaf nodes uniformly.
So whether an element is a composite or leaf node becomes transparent to the client.
If a safer design is required, we could take separate out the responsibilities into separate interfaces, instead of an common Component interface. This way, any inappropriate calls would be caught at compile time or runtime, but transparency would be lost and the code would have to use conditionals and the instanceof operator.
Order
Another issue that is ordering of children. In case the children of a composite need to be in a specific order,
we require a more sophisticated management scheme for adding and removing children, and how to traverse the hierarchy.
Caching
If the composite structure is complex or expensive to traverse, it’s helpful to implement caching of the composite nodes.
For instance, if you are constantly traversing a composite and all its children to compute some result, you could implement a cache that stores the result temporarily to save traversals.
Composite Example
The following example extends the previous example from the Iterator Pattern, which shows two restraunt menus, where both implement the same aggregate interface Menu. Each menu has menu items stored in different types of collections (aggregates such as ArrayList, standard array or HashMap).
With the Iterator Pattern it is possible to iterate over these items without knowing the underlying type of the aggregate.
Let’s start with the MenuComponent class, which is the base class of Leaf, which describes the MenuItem, and Composite, which is a complete Menu in this example.
Each menu will have one or more menu items that implement the MenuComponent interface and play the role of Leafs in the composite:
As in the previous example, each aggregate implements the createIterator() method which is declared in the Menu interface. This time, the interface is a subclass of MenuComponent and acts as a Composite:
Inside the print() method, an Iterator is used to iterate through all the Menu’s components - other Menus or MenuItems. If another Menu object appears during this iteration, its print() method will start another iteration,
that will print the whole tree structure.
All the client, in this example the Waitress, needs is the top-level menu component, that contains all the other menus, called allmenus:
To print the entire menu hierarchy, all the menus and all the menu items, we need to call printMenu() on the Waitress.
To define our menu we create the tree structure in the test program MenuTestDrive,
instead of having two menus that implement Menu. Here we first create all the menu objects including a top-level menu,
named allMenus. The tree structure is created with the Composite.add() method. With this method it is also possible to add a menu to a menu because menus and menu items use the same MenuComponent interface.
At the end of this test program, the complete tree structure is handed to the Waitress which calls printMenu() that produces the following result:
This implementation uses an internal Iterator in the Menu class, which is
created from the member menuComponents that is of type ArrayList<MenuComponent>.
This way we, applying print() we get a printout of the whole composite.
This iterator steps through each item in the component, and if an item is a Menu rather than a MenuItem,
then print() gets called recursively to handle printing.
This way, the MenuComponent interface handles the iteration itself internally.
External Iterator over Composite
To allow the client, the Waitress, more control to iterate over components, we can use an external iterator.
This will make it possible to go through the entire menu and pull out vegetarian items.
An external iterator must maintain its position in the iteration so that an outside client can drive the iteration by calling hasNext() and next(). In this case the external iterator implementation needs to maintain the position over the composite, recursive structure. The following example shows how to achieve this using stacks to maintain the position as we move up and down the composite hierarchy.
To achieve this, we have to add the method createIterator() to the MenuComponent, which means that each Menu and MenuItem will need to implement this method. Calling this method on a composite should apply to all children of the composite.
MenuItem also implements the new method createIterator():
Here we return a NullIterator because there is nothing to iterate over in a Leaf node.
The implementation of the NullIterator which is a Null Object design pattern,
follows in the next code snippet. This special iterator always returns false when hasNext() is called:
Now the following implementation of CompositeIterator makes it possible to iterate over the tree structure.
To get the vegetarian menu the Waitress implements a new printVegetarianMenu() method:
Executing this method in a test program results in the following output:
Comments