Menu
Coddy logo textTech

컴포지트 패턴

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

Composite 패턴을 사용하면 개별 객체와 객체 그룹을 일관되게 다룰 수 있습니다. 이 패턴은 객체를 트리 구조로 구성하여 단일 요소와 요소 컨테이너가 동일한 인터페이스를 공유하도록 합니다. 이는 파일 시스템, 조직도 또는 UI components와 같은 계층 구조를 표현하는 데 이상적입니다.

이 패턴은 세 가지 핵심 부분으로 구성됩니다. 공통 연산을 정의하는 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 composite는 하위 항목을 저장하고, 해당 항목들을 순회하여 작업을 구현합니다. 폴더에서 getSize()를 호출하면 폴더에 포함된 모든 항목의 전체 크기를 재귀적으로 계산합니다. 클라이언트 코드는 파일로 작업하는지 폴더로 작업하는지 알 필요가 없습니다. 둘 다 동일한 인터페이스에 응답하기 때문입니다.

부분-전체 계층 구조를 나타내야 하고 클라이언트가 개별 객체와 구성을 동일하게 처리하도록 하려면 Composite를 사용하세요.

challenge icon

챌린지

쉬움

Composite 패턴을 사용하여 Organization Chart 시스템을 만들어 봅시다. 개별 employees와 다른 employees 또는 하위 departments를 포함하는 departments를 모두 동일한 방식으로 다룰 수 있는 계층 구조를 만들게 됩니다. 이는 departments가 사람들과 다른 departments를 포함하여 트리 구조를 형성하는 실제 회사의 구조를 반영합니다.

코드를 세 개의 파일로 구성합니다:

  • OrgComponent.h: employees와 departments가 모두 구현할 component 인터페이스를 정의합니다.

    다음을 포함하는 abstract OrgComponent class를 만듭니다:

    • getName(): component의 이름을 반환합니다
    • getSalary(): 총 salary를 반환합니다(employees의 경우 자신의 salary, departments의 경우 포함된 모든 salary의 합계)
    • display(int indent = 0): 적절한 들여쓰기를 적용하여 component를 표시합니다

    virtual destructor를 포함합니다.

  • Organization.h: leaf 및 composite classes를 구현합니다.

    이름과 salary를 저장하는 Employee class(the leaf)를 만듭니다. 이 class의 display() method는 다음 형식으로 employee의 정보를 출력해야 합니다:

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

    이름과 std::shared_ptr를 사용하는 OrgComponent children의 collection을 저장하는 Department class(the composite)를 만듭니다. 다음을 구현합니다:

    • add(std::shared_ptr<OrgComponent> component): department에 child를 추가합니다
    • getSalary(): 모든 members의 총 salary를 재귀적으로 계산합니다
    • display(): department 이름을 대괄호 안에 출력한 다음, 들여쓰기를 늘려 모든 children을 표시합니다(레벨마다 공백 2개 추가)

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

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

    네 개의 입력을 읽습니다:

    1. Company name (string)
    2. Department name (string)
    3. First employee name and salary (format: name,salary)
    4. Second employee name and salary (format: name,salary)

    다음 구조를 구성합니다: company(top-level department)를 만들고, 여기에 sub-department를 추가한 다음, 두 employees를 모두 해당 sub-department에 추가합니다. 그런 다음 전체 organization을 표시하고 company의 총 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()가 employee에서 호출되든 department에서 호출되든 동일한 방식으로 작동한다는 점에 주목하세요. department는 모든 members의 salary를 자동으로 집계합니다. 총합을 계산하거나 계층 구조를 표시할 때 client code는 개별 employees와 전체 departments를 구분할 필요가 없습니다.

직접 해보기

#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: 하위 부서 생성
    
    // TODO: 파싱된 데이터를 사용해 두 개의 Employee 객체 생성
    
    // TODO: 하위 부서에 직원 추가
    
    // TODO: 회사에 하위 부서 추가
    
    // TODO: 전체 조직 구조 표시
    
    // TODO: Total Salary: $[amount] 형식으로 총 급여 출력
    
    return 0;
}
quiz icon실력 점검

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

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

직접 연습해 보세요: 온라인 C++ 컴파일러