Composite Pattern
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 101 of 104.
The Composite pattern lets you treat individual objects and groups of objects uniformly. It composes objects into tree structures where both single elements and containers of elements share the same interface. This is ideal for representing hierarchies like file systems, organization charts, or UI components.
The pattern has three key parts: a Component interface that defines common operations, Leaf classes representing individual objects, and Composite classes that contain children and delegate operations to them:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Component interface
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Leaf - represents individual files
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Composite - contains other components
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};The Folder composite stores children and implements operations by iterating through them. When you call getSize() on a folder, it recursively calculates the total size of all contained items. The client code doesn't need to know whether it's working with a file or a folder - both respond to the same interface.
Use Composite when you need to represent part-whole hierarchies and want clients to treat individual objects and compositions uniformly.
Challenge
EasyLet's build an Organization Chart system using the Composite pattern. You'll create a hierarchy where both individual employees and departments (which contain other employees or sub-departments) can be treated uniformly. This mirrors how real companies are structured—departments contain people and other departments, forming a tree structure.
You'll organize your code across three files:
OrgComponent.h: Define the component interface that both employees and departments will implement.Create an abstract
OrgComponentclass with:getName()— returns the component's namegetSalary()— returns the total salary (for employees, their own salary; for departments, the sum of all contained salaries)display(int indent = 0)— displays the component with proper indentation
Include a virtual destructor.
Organization.h: Implement the leaf and composite classes.Create an
Employeeclass (the leaf) that stores a name and salary. Itsdisplay()method should print the employee's information in this format:[indent spaces]- [name] ($[salary])Create a
Departmentclass (the composite) that stores a name and a collection ofOrgComponentchildren usingstd::shared_ptr. Implement:add(std::shared_ptr<OrgComponent> component)— adds a child to the departmentgetSalary()— recursively calculates the total salary of all membersdisplay()— prints the department name in brackets, then displays all children with increased indentation (add 2 spaces per level)
The department's display format should be:
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: Build and display an organization structure.Read four inputs:
- Company name (string)
- Department name (string)
- First employee name and salary (format:
name,salary) - Second employee name and salary (format:
name,salary)
Build this structure: Create a company (top-level department), add a sub-department to it, and add both employees to that sub-department. Then display the entire organization and print the total company salary.
After displaying the structure, print:
Total Salary: $[amount]
For example, with inputs TechCorp, Engineering, Alice,75000, and Bob,65000:
[TechCorp]
[Engineering]
- Alice ($75000)
- Bob ($65000)
Total Salary: $140000With inputs StartupInc, Development, Carol,80000, and Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Notice how getSalary() works uniformly whether called on an employee or a department—the department automatically aggregates salaries from all its members. The client code doesn't need to distinguish between individual employees and entire departments when calculating totals or displaying the hierarchy.
Cheat sheet
The Composite pattern treats individual objects and groups of objects uniformly by composing them into tree structures where both single elements and containers share the same interface.
The pattern has three key parts:
- Component: An interface defining common operations
- Leaf: Classes representing individual objects
- Composite: Classes that contain children and delegate operations to them
Example implementation of a file system:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Component interface
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Leaf - represents individual files
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Composite - contains other components
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};The composite stores children using std::shared_ptr and implements operations by iterating through them. Operations like getSize() work recursively, calculating totals across the entire hierarchy.
Use Composite when you need to represent part-whole hierarchies and want clients to treat individual objects and compositions uniformly.
Try it yourself
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Read inputs
std::string companyName;
std::string departmentName;
std::string employee1Input;
std::string employee2Input;
std::getline(std::cin, companyName);
std::getline(std::cin, departmentName);
std::getline(std::cin, employee1Input);
std::getline(std::cin, employee2Input);
// Helper lambda to parse "name,salary" format
auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
size_t commaPos = input.find(',');
std::string name = input.substr(0, commaPos);
int salary = std::stoi(input.substr(commaPos + 1));
return {name, salary};
};
auto [name1, salary1] = parseEmployee(employee1Input);
auto [name2, salary2] = parseEmployee(employee2Input);
// TODO: Create the company as a top-level Department
// TODO: Create a sub-department
// TODO: Create two Employee objects using the parsed data
// TODO: Add employees to the sub-department
// TODO: Add the sub-department to the company
// TODO: Display the entire organization structure
// TODO: Print the total salary in format: Total Salary: $[amount]
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRAII as a Pattern3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container