Menu
Coddy logo textTech

컴포지트 패턴

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 101번째.

컴포지트 패턴(Composite pattern)은 개별 객체와 객체 그룹을 동일하게 다룰 수 있도록 해줍니다. 이 패턴은 단일 요소와 요소의 컨테이너가 모두 동일한 인터페이스를 공유하는 트리 구조로 객체를 구성합니다. 이는 파일 시스템, 조직도 또는 UI 컴포넌트와 같은 계층 구조를 표현하는 데 이상적입니다.

이 패턴은 세 가지 핵심 부분으로 구성됩니다: 공통 작업을 정의하는 Component 인터페이스, 개별 객체를 나타내는 Leaf 클래스, 그리고 자식 요소를 포함하고 작업을 이들에게 위임하는 Composite 클래스입니다:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// 컴포넌트 인터페이스
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// Leaf - 개별 파일을 나타냄
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 - 다른 컴포넌트들을 포함함
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;
    }
};

Folder 컴포지트는 자식들을 저장하고 이들을 순회하며 작업을 구현합니다. 폴더에서 getSize()를 호출하면, 포함된 모든 항목의 총 크기를 재귀적으로 계산합니다. 클라이언트 코드는 파일과 작업 중인지 폴더와 작업 중인지 알 필요가 없습니다. 둘 다 동일한 인터페이스에 응답하기 때문입니다.

부분-전체 계층 구조를 표현해야 하고, 클라이언트가 개별 객체와 복합 객체를 동일하게 다루기를 원할 때 컴포지트(Composite) 패턴을 사용하세요.

challenge icon

챌린지

쉬움

Composite 패턴을 사용하여 조직도(Organization Chart) 시스템을 구축해 봅시다. 개별 직원과 부서(다른 직원이나 하위 부서를 포함하는)를 동일하게 취급할 수 있는 계층 구조를 만들 것입니다. 이는 실제 회사의 구조와 유사합니다. 부서는 사람과 다른 부서를 포함하며 트리 구조를 형성합니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • OrgComponent.h: 직원과 부서가 모두 구현할 컴포넌트 인터페이스를 정의합니다.

    다음 기능을 가진 추상 클래스 OrgComponent를 생성하세요:

    • getName() — 컴포넌트의 이름을 반환합니다.
    • getSalary() — 총 급여를 반환합니다 (직원의 경우 자신의 급여, 부서의 경우 포함된 모든 구성원 급여의 합계).
    • display(int indent = 0) — 적절한 들여쓰기와 함께 컴포넌트를 표시합니다.

    가상 소멸자를 포함하세요.

  • Organization.h: Leaf 클래스와 Composite 클래스를 구현합니다.

    이름과 급여를 저장하는 Employee 클래스(Leaf)를 생성하세요. display() 메서드는 직원의 정보를 다음 형식으로 출력해야 합니다:

    [indent spaces]- [name] ($[salary])

    이름과 std::shared_ptr을 사용하는 OrgComponent 자식들의 컬렉션을 저장하는 Department 클래스(Composite)를 생성하세요. 다음을 구현하세요:

    • add(std::shared_ptr<OrgComponent> component) — 부서에 자식을 추가합니다.
    • getSalary() — 모든 구성원의 총 급여를 재귀적으로 계산합니다.
    • display() — 대괄호 안에 부서 이름을 출력한 다음, 들여쓰기를 늘려(레벨당 공백 2개 추가) 모든 자식을 표시합니다.

    부서의 표시 형식은 다음과 같아야 합니다:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: 조직 구조를 구축하고 표시합니다.

    네 개의 입력을 읽습니다:

    1. 회사 이름 (문자열)
    2. 부서 이름 (문자열)
    3. 첫 번째 직원의 이름과 급여 (형식: name,salary)
    4. 두 번째 직원의 이름과 급여 (형식: name,salary)

    다음 구조를 구축하세요: 회사(최상위 부서)를 생성하고, 여기에 하위 부서를 추가한 다음, 두 직원 모두를 해당 하위 부서에 추가합니다. 그런 다음 전체 조직을 표시하고 회사의 총 급여를 출력합니다.

    구조를 표시한 후, 다음을 출력하세요:

    Total Salary: $[amount]

예를 들어, 입력이 TechCorp, Engineering, Alice,75000, Bob,65000인 경우:

[TechCorp]
  [Engineering]
    - Alice ($75000)
    - Bob ($65000)
Total Salary: $140000

입력이 StartupInc, Development, Carol,80000, Dave,70000인 경우:

[StartupInc]
  [Development]
    - Carol ($80000)
    - Dave ($70000)
Total Salary: $150000

getSalary()가 직원에게 호출되든 부서에 호출되든 동일하게 작동하는 방식에 주목하세요. 부서는 자동으로 모든 구성원의 급여를 합산합니다. 클라이언트 코드는 합계를 계산하거나 계층 구조를 표시할 때 개별 직원과 전체 부서를 구분할 필요가 없습니다.

치트 시트

컴포지트 패턴(Composite pattern)은 개별 객체와 객체 그룹을 트리 구조로 구성하여, 단일 요소와 컨테이너가 동일한 인터페이스를 공유하도록 함으로써 이들을 균일하게 처리합니다.

이 패턴은 세 가지 핵심 부분으로 구성됩니다:

  • Component (컴포넌트): 공통 작업을 정의하는 인터페이스
  • Leaf (리프): 개별 객체를 나타내는 클래스
  • Composite (컴포지트): 자식을 포함하며 작업을 자식에게 위임하는 클래스

파일 시스템의 구현 예시:

#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;
    }
};

컴포지트는 std::shared_ptr를 사용하여 자식들을 저장하고, 이들을 순회하며 작업을 구현합니다. getSize()와 같은 작업은 재귀적으로 작동하여 전체 계층 구조에 걸친 합계를 계산합니다.

부분-전체 계층 구조를 표현해야 하고, 클라이언트가 개별 객체와 복합 객체를 동일하게 처리하기를 원할 때 컴포지트 패턴을 사용하세요.

직접 해보기

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"

int main() {
    // 입력 읽기
    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);
    
    // "name,salary" 형식을 파싱하기 위한 헬퍼 람다
    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: 회사를 최상위 Department로 생성
    
    // TODO: 하위 부서(sub-department) 생성
    
    // TODO: 파싱된 데이터를 사용하여 두 개의 Employee 객체 생성
    
    // TODO: 하위 부서에 직원 추가
    
    // TODO: 회사(최상위 부서)에 하위 부서 추가
    
    // TODO: 전체 조직 구조 표시
    
    // TODO: 총 급여를 Total Salary: $[amount] 형식으로 출력
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨