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 kg

Step-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 : children

Common 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).