Composite
Arrange objects into a tree and give leaves and containers the same interface, so the client can treat one item and a whole branch identically.
The problem
You are building an ordering system with Product and Box. A box holds products, and
also smaller boxes, which hold products, and possibly still smaller boxes. Someone asks
for the total price of an order.
In the physical world you would simply tear everything open and add up the labels. In code you would have to know the classes involved, the nesting depth, and which type check to run at each level, before writing the loop. Then you would write it again, from scratch, for total weight. And again for the packing slip.
The solution
Give products and boxes one shared interface with a getPrice() method. A product
returns its price. A box asks each of its contents for a price, adds them up, maybe adds
packaging cost, and returns the total. If one of those contents is another box, it does
the same thing one level deeper, and so on until the recursion runs out of tree.
The payoff is that the client stops caring about concrete classes entirely. Call the method on the top node and the objects themselves route the request downward.
- 1The client fills the tree through the component interface. A dot and a group are the same kind of argument.
- 2A container gets added to a container. Nothing special happens, which is exactly the point.
- 3One call on one object. The client has no idea how deep this goes.
- 4The root loops over its children and forwards the request, never asking what class anything is.
- 5The group does the same thing one level down. Recursion is the implementation and the point.
- 6The leaf does the real drawing and reports its bounding box back up.
- 7Each container folds its children’s results into one and passes the summary onward.
- 8The whole tree rendered from a single call, with the client coupled to nothing but Graphic.
Structure
Four roles: the component interface shared by everything, leaves with no children that do the real work, containers that hold components and delegate, and the client that talks to the whole tree as if it were one object.
Code
A graphics editor where a dot, a circle and a group of a hundred shapes are all just a
Graphic.
When to use it
- The model is a tree: file systems, org charts, UI widget hierarchies, nested orders, scene graphs, expression trees.
- You want client code to treat a single element and a whole subtree the same way, so that adding a new node type does not send you editing every consumer.
Pitfalls
- The overgeneralized component. If leaves and containers differ too much, the shared interface turns into a lowest common denominator that describes nothing well.
- Child management on the component interface.
add()on a leaf is a lie you will have to implement somehow. Choose uniformity or honesty and document which. - Cycles. Nothing in the pattern stops you adding a container to its own subtree. The
first symptom is a stack overflow on the next
draw(). - Silent cost. A single innocent-looking call may touch tens of thousands of nodes. Caching aggregate results is a common follow-up, and invalidating them is the usual next bug.
Don't confuse it with
- Decorator. Structurally a decorator is a composite with exactly one child. The difference is motive: a decorator adds responsibilities on the way through, while a composite sums up what its children report. They cooperate happily - decorate one node inside a composite tree and nobody else notices.
- Chain of Responsibility. CoR passes a request along until somebody handles it, then stops. Composite fans out to everyone and aggregates. Combined, they are how a leaf bubbles an event up to the root.
- Iterator. An iterator traverses a structure from outside; Composite is the structure, traversing itself from inside. Use an Iterator to walk a Composite tree, or a Visitor to run an operation over all of it.
- Flyweight. Not a rival: shared leaf nodes in a big Composite tree are prime candidates to be implemented as flyweights when memory gets tight.
Check yourself
What is the precondition for reaching for Composite?