[C/C++] 컴포지트 패턴(Composite Pattern)

할랑말랑·2026년 3월 24일

C/C++

목록 보기
36/45

컴포지트 패턴(Composite Pattern)

복합 객체(Composite) 와 단일 객체(Leaf)를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는 구조 패턴

1. 구성 요소

  • Component : 모든 구체적인 객체들이 구현해야 하는 공통 인터페이스
  • Leaf (개별 객체) : 트리 구조의 말단 노드로, 자식 요소를 가질 수 없습니다. 실제 비즈니스 로직을 수행
  • Composite (그룹 객체) : Leaf 또는 다른 Composite를 포함하는 객체

2. 장점

  • OCP : 기능 추가 쉬움, 유지보수 편함
  • 계층 구조 표현 명확 : 실세계 모델링 쉬움, 조직도, 파일 시스템 등 자연스러움
  • 다형성 재귀를 통해 복잡한 트리 구조를 보다 편리하게 구성

3. 단점

  • 과도한 일반화 : 의미 없는 메서드 생김, 인터페이스 비대화
  • 설계가 지나치게 범용성을 갖기 때문에 새로운 요소를 추가할 때 복합 객체에서 구성 요소에 제약을 갖기 힘들다

Composite Pattern : 인벤토리

  • Component : ItemComponent
  • Leaf : Item
  • Composite : ItemGroup

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

enum class ItemType
{
    Consumables,
    Equipment,
    Misc
};

// 개별 객체(Leaf)와 집합 객체(Composite)를 묶는 공통 인터페이스 (Component)
class ItemComponent
{
public:
    virtual ~ItemComponent() = default;
    virtual void GetItem() = 0;
};

// 개별 객체를 나타내는 클래스 (Leaf)
class Item : public ItemComponent
{
private:
    std::string name;
public:
    Item(const std::string& _name) : name(_name) {}
    void GetItem() override { std::cout << " 아이템 : " << name << std::endl; }
};

// 집합 객체를 나타내는 클래스 (Composite)
class ItemGroup : public ItemComponent
{
private:
    std::string name;
    std::vector<std::unique_ptr<ItemComponent>> items;
public:
    ItemGroup(const std::string& _name) : name(_name) {}
    void AddItem(std::unique_ptr<ItemComponent> _item)
    {
        items.push_back(std::move(_item));
    }
    void GetItem() override
    {
        std::cout << name << std::endl;
        // 자신이 포함하는 모든 자식들의 GetItem()을 재귀적으로 호출
        for (const auto& item : items)
        {
            item->GetItem();
        }
    }
};

// 컴포지트 패턴을 사용하는 클라이언트 클래스
class Inventory
{
private:
    std::unique_ptr<ItemGroup> consumables;
    std::unique_ptr<ItemGroup> equipment;
    std::unique_ptr<ItemGroup> misc;
public:
    Inventory() : consumables(std::make_unique<ItemGroup>("<소모품>")),
                  equipment(std::make_unique<ItemGroup>("<장비>")),
                  misc(std::make_unique<ItemGroup>("<기타>")) {}

    void AddItem(std::unique_ptr<ItemComponent> _item, const ItemType& _type)
    {
        switch (_type)
        {
        case ItemType::Consumables:
            consumables->AddItem(std::move(_item));
            break;
        case ItemType::Equipment:
            equipment->AddItem(std::move(_item));
            break;
        case ItemType::Misc:
            misc->AddItem(std::move(_item));
            break;
        }
    }

    void GetItem()
    {
        std::cout << "<<인벤토리>>" << std::endl;
        consumables->GetItem();
        equipment->GetItem();
        misc->GetItem();
    }
};

int main()
{
    Inventory inven;
    inven.AddItem(std::make_unique<Item>("검"), ItemType::Equipment);
    inven.AddItem(std::make_unique<Item>("활"), ItemType::Equipment);
    inven.AddItem(std::make_unique<Item>("방패"), ItemType::Equipment);

    inven.AddItem(std::make_unique<Item>("체력물약"), ItemType::Consumables);
    inven.AddItem(std::make_unique<Item>("마나물약"), ItemType::Consumables);
    inven.AddItem(std::make_unique<Item>("버프물약"), ItemType::Consumables);

    inven.AddItem(std::make_unique<Item>("돌"), ItemType::Misc);
    inven.AddItem(std::make_unique<Item>("풀"), ItemType::Misc);
    inven.AddItem(std::make_unique<Item>("흙"), ItemType::Misc);

    inven.GetItem();

    return 0;
}

  • 통일된 인터페이스 : Item과 ItemGroup이 모두 ItemComponent를 상속받아 GetItem() 메서드를 가지고있다. 이 덕분에 클라이언트(Inventory)는 객체가 단일 아이템인지, 아이템 그룹인지 신경 쓸 필요 없이 동일한 방식으로 다룰 수 있다.
  • 재귀적 구조 : ItemGroup의 GetItem() 메서드는 자신이 가진 자식 요소들의 GetItem()을 다시 호출, 만약 자식이 또 다른 ItemGroup이라면 이 과정이 재귀적으로 반복되어 전체 트리 구조를 순회할 수 있다.
  • 유연한 구조: Inventory -> ItemGroup ("소모품", "장비", "기타") -> Item ("검", "활", "방패"...) 으로 이어지는 계층적인 구조를 매우 쉽게 표현하고 관리할 수 있다.

0개의 댓글