본문으로 건너뛰기 C++ Composite Pattern Complete Guide

C++ Composite Pattern Complete Guide

C++ Composite Pattern Complete Guide

이 글의 핵심

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.

// 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.

// 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;
    }
};
flowchart TD
    client[Client]
    component[Component\n(getSize)]
    leaf[Leaf\n(File)]
    composite[Composite\n(Folder)]
    
    client --> component
    leaf -.implements.-> component
    composite -.implements.-> component
    composite --> component

Table of Contents

  1. Basic Structure
  2. File System Example
  3. UI Component Hierarchy
  4. Common Problems and Solutions
  5. Production Patterns
  6. 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

  1. Composite Pattern: Treats individual objects and compositions uniformly
  2. Component: Abstract base class defining common interface
  3. Leaf: Individual object with no children
  4. Composite: Container holding children
  5. 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


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ Composite Pattern Complete Guide: Handling tree structures with uniform interface. What is Composite pattern? Why ne… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, Composite, design pattern, structural, tree, hierarchy, recursive 등으로 검색하시면 이 글이 도움이 됩니다.