Composite
Learning Objectives
Represent part–whole trees and treat leaves and composites uniformly.
Implement recursive operations over a tree.
Prerequisites: Modules 8, 9.
Intuition and Definition
Intent: compose objects into tree structures to represent part–whole hierarchies; let clients treat individual objects and compositions uniformly.
Participants: Component (common interface), Leaf (no children), Composite (holds child components and implements operations by delegating to them).
Why It Matters
Menus, file systems, UI layouts, organisation charts and vehicle bill-of-materials are trees. With Composite, "total cost" or "render" is one call on the root.
Syntax and Language Notes
The key design choice is where to put child-management methods (add/remove): on Component (uniform but leaves must reject them) or only on Composite (type-safe but less uniform). Modern code usually chooses type safety.
Complete Example
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Part { // Component
public:
virtual ~Part() = default;
virtual double cost() const = 0;
virtual double massKg() const = 0;
virtual void print(int depth) const = 0;
};
class Component : public Part { // Leaf
std::string name_; double cost_, mass_;
public:
Component(std::string n, double c, double m) : name_(std::move(n)), cost_(c), mass_(m) {}
double cost() const override { return cost_; }
double massKg() const override { return mass_; }
void print(int d) const override { std::cout << std::string(static_cast<size_t>(d * 2), ' ') << "- " << name_ << " $" << cost_ << "\n"; }
};
class Assembly : public Part { // Composite
std::string name_;
double labour_;
std::vector<std::unique_ptr<Part>> children_;
public:
Assembly(std::string n, double labour) : name_(std::move(n)), labour_(labour) {}
Assembly& add(std::unique_ptr<Part> p) { children_.push_back(std::move(p)); return *this; }
double cost() const override { double c = labour_; for (const auto& p : ch
…+ Front-left corner (labour $30)
+ Wheel (labour $15)
- Rim $120
- Tyre $95
+ Brake (labour $20)
- Disc $60
- Caliper $85
- Hub bearing $45
total cost $470, mass 28.5 kgStep-by-Step Walkthrough
Leaves report their own cost and mass; assemblies sum their children plus their own labour.
corner.cost() recursively visits the whole tree: 30 + (15+120+95) + (20+60+85) + 45 = 470.
The client never checks "is this a leaf?" — uniform treatment.
classDiagram
class Part {
<<abstract>>
+cost() double
+massKg() double
}
Part <|-- Component
Part <|-- Assembly
Assembly o-- "0..*" Part : childrenCommon Mistakes
Cycles (an assembly containing itself) → infinite recursion; enforce a tree.
Deep recursion on very deep trees → stack overflow; use iteration for huge depths.
Putting add() on leaves that throw at run time.
Default arguments on virtual functions. They are bound to the *static* type: had Part::print declared int depth = 0, the call corner.print() on an Assembly would not compile (the override declares no default), and calling through a Part& would use the base's default even if the override declared a different one. Avoid defaults on virtuals.
Trade-offs and Alternatives
Composite makes adding new *component types* easy and new *operations* harder (each operation is a virtual in every class) — Visitor (Module 49) reverses that.
Real-World Use
DOM trees, UI layout containers, file systems (files and directories), scene graphs, bill-of-materials, organisation hierarchies, and expression trees (Interpreter, Module 50).
Practice
Q1. (Easy) What two roles implement the component interface?
Answer: Leaf and Composite.
Q2. (Medium) How does a composite implement cost()?
Answer: By combining (summing) its children's results plus its own contribution.
Q3. (Advanced) Add a count() operation that returns the number of leaf components. Which classes change?
Answer: Part gains virtual int count() const = 0;, Component returns 1, and Assembly sums its children. All three change — new operations touch every class in Composite.
Interview Question
Q1. "Should add()/remove() be declared on the Component interface?"
Answer: It's a trade-off. On Component: transparency (clients treat everything uniformly), but leaves must implement them by throwing or ignoring — run-time errors. Only on Composite: type safety (can't add to a leaf at compile time), but clients must know which they hold when building the tree. Most modern designs prefer type safety: build trees with concrete composite types and use the uniform interface only for operations.
Quick Quiz
Q1. What structure does Composite model?
Answer: Part–whole trees.
Q2. What prevents infinite recursion?
Answer: Keeping the structure a tree (no cycles).
Q3. Which pattern makes adding operations to a composite structure easier?
Answer: Visitor.
Summary
Composite treats leaves and composites uniformly through a common interface.
Operations recurse through the tree; keep it acyclic.
Related: Modules 46 (Iterator), 49 (Visitor), 50 (Interpreter).