C++ Composite Pattern Complete Guide | Handling Tree Structures with Uniform Interface
이 글의 핵심
C++ Composite Pattern: Handling tree structures with uniform interface. What is Composite pattern? Why needed, basic structure.
What is Composite Pattern? Why Needed
The flow of unifying tree and part-whole with one interface is good to read compared with other patterns in the Structural Pattern Series.
Problem Scenario: Processing Individual Objects and Groups Differently
Problem: Processing files and folders in different ways makes code complex. Below is an implementation example using C++. Process data with loops and perform branching with conditionals. Understand the role of each part while examining the code.
// Bad design: Type checking needed
void printSize(FileSystemItem* item) {
if (auto* file = dynamic_cast<File*>(item)) {
std::cout << file->getSize() << '\n';
} else if (auto* folder = dynamic_cast<Folder*>(item)) {
for (auto& child : folder->getChildren()) {
printSize(child); // Recursion
}
}
}
Solution: Composite pattern handles leaf (file) and composite (folder) with the same interface. Here is detailed implementation code using C++. Define a class to encapsulate data and functionality, and process data with loops. Understand the role of each part while examining the code.
// Good design: Composite
// Type definition
class Component {
public:
virtual int getSize() const = 0; // Unified interface
};
class File : public Component {
int size_;
public:
int getSize() const override { return size_; }
};
class Folder : public Component {
std::vector<std::shared_ptr<Component>> children_;
public:
int getSize() const override {
int total = 0;
for (const auto& child : children_)
total += child->getSize(); // Recursion
return total;
}
};
Below is an implementation example using mermaid. Understand the role of each part while examining the code.
flowchart TD
client[Client]
component[Component
(getSize)]
leaf[Leaf
(File)]
composite[Composite
(Folder)]
client --> component
leaf -.implements.-> component
composite -.implements.-> component
composite --> component
Table of Contents
- Basic Structure
- File System Example
- UI Component Hierarchy
- Common Problems and Solutions
- Production Patterns
- Complete Example: Organization Chart System
1. Basic Structure
#include <vector>
#include <memory>
#include <iostream>
class Component {
public:
virtual void operation() const = 0;
virtual void add(std::shared_ptr<Component>) {}
virtual void remove(std::shared_ptr<Component>) {}
virtual ~Component() = default;
};
// Leaf: No children
class Leaf : public Component {
int id_;
public:
explicit Leaf(int id) : id_(id) {}
void operation() const override {
std::cout << "Leaf " << id_ << '\n';
}
};
// Composite: Has children list
class Composite : public Component {
std::vector<std::shared_ptr<Component>> children_;
public:
void add(std::shared_ptr<Component> c) override {
children_.push_back(std::move(c));
}
void remove(std::shared_ptr<Component> c) override {
children_.erase(
std::remove(children_.begin(), children_.end(), c),
children_.end()
);
}
void operation() const override {
std::cout << "Composite [\n";
for (const auto& c : children_)
c->operation();
std::cout << "]\n";
}
};
int main() {
auto root = std::make_shared<Composite>();
root->add(std::make_shared<Leaf>(1));
auto branch = std::make_shared<Composite>();
branch->add(std::make_shared<Leaf>(2));
branch->add(std::make_shared<Leaf>(3));
root->add(branch);
root->operation();
// Output:
// Composite [
// Leaf 1
// Composite [
// Leaf 2
// Leaf 3
// ]
// ]
return 0;
}
2. File System Example
Calculating File and Folder Sizes
#include <vector>
#include <memory>
#include <iostream>
#include <string>
class FileSystemItem {
public:
virtual int getSize() const = 0;
virtual void print(int indent = 0) const = 0;
virtual ~FileSystemItem() = default;
};
class File : public FileSystemItem {
std::string name_;
int size_;
public:
File(std::string name, int size) : name_(std::move(name)), size_(size) {}
int getSize() const override { return size_; }
void print(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "File: " << name_
<< " (" << size_ << " bytes)\n";
}
};
class Folder : public FileSystemItem {
std::string name_;
std::vector<std::shared_ptr<FileSystemItem>> children_;
public:
explicit Folder(std::string name) : name_(std::move(name)) {}
void add(std::shared_ptr<FileSystemItem> item) {
children_.push_back(std::move(item));
}
int getSize() const override {
int total = 0;
for (const auto& child : children_)
total += child->getSize();
return total;
}
void print(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "Folder: " << name_
<< " (" << getSize() << " bytes total)\n";
for (const auto& child : children_)
child->print(indent + 2);
}
};
int main() {
auto root = std::make_shared<Folder>("root");
root->add(std::make_shared<File>("readme.txt", 100));
auto src = std::make_shared<Folder>("src");
src->add(std::make_shared<File>("main.cpp", 500));
src->add(std::make_shared<File>("utils.cpp", 300));
root->add(src);
auto docs = std::make_shared<Folder>("docs");
docs->add(std::make_shared<File>("manual.pdf", 2000));
root->add(docs);
root->print();
// Output:
// Folder: root (2900 bytes total)
// File: readme.txt (100 bytes)
// Folder: src (800 bytes total)
// File: main.cpp (500 bytes)
// File: utils.cpp (300 bytes)
// Folder: docs (2000 bytes total)
// File: manual.pdf (2000 bytes)
return 0;
}
Key Point: getSize() is called recursively to calculate the size of the entire tree.
Summary
Key Points
- Composite Pattern: Treats individual objects and compositions uniformly
- Component: Abstract base class defining common interface
- Leaf: Individual object with no children
- Composite: Container holding children
- Recursion: Operations propagate through tree structure
When to Use
✅ Use Composite when:
- Need to represent part-whole hierarchies
- Want clients to treat individual and composite objects uniformly
- Tree structures (file systems, UI components, organization charts) ❌ Don’t use when:
- Flat structure with no hierarchy
- Different operations for leaf and composite
- Performance critical (recursion overhead)
Best Practices
- ✅ Use smart pointers for memory management
- ✅ Implement visitor pattern for complex operations
- ✅ Consider caching for expensive calculations
- ❌ Don’t expose internal structure
- ❌ Don’t forget virtual destructors
Related Articles
- C++ Facade Pattern
- C++ Bridge Pattern
- C++ Structural Patterns Master tree structure handling with Composite pattern! 🚀