Unity

[디자인 패턴] - 복합체 패턴(Composite)

CCS_Cheese 2026. 3. 15. 16:39

문제 상황 (Problem)

쇼핑몰 주문 시스템이나 파일 시스템을 개발한다고 가정해 보겠습니다.

상황:

  • 상품(Product) 은 가격을 가지고 있습니다.
  • 박스(Box)는 여러 개의 상품을 담을 수 있고, 또 다른 작은 박스도 담을 수 있습니다.
  • 클라이언트는 "최종 가격"을 계산하고 싶어 합니다.

발생하는 문제:

  • 만약 패턴을 쓰지 않는다면, 클라이언트는 "이것이 상품인가? 아니면 박스인가?"를 일일이 확인해야 합니다.
  • 박스라면 내부를 다시 순회해야 하고, 상품이라면 가격을 가져와야 합니다. 박스 안에 박스가 또 있다면 코드는 중첩된 if-else문이나 복잡한 재귀 호출로 지저분해집니다.
  • 새로운 종류의 '묶음 상품'이 추가되면 클라이언트 코드를 또 수정해야 합니다.

해결 방법 (Solution)

복합체 패턴은 "전체-부분(Whole-Part)" 관계를 가진 객체들을 트리 구조로 정의하고, 공통 인터페이스를 사용하여 이들을 똑같이 취급함으로써 문제를 해결합니다.

  • Component (공통 인터페이스): 상품과 박스가 공통으로 가질 메서드(예: getPrice())를 정의합니다.
  • Leaf (단일 객체): 더 이상 하위 요소가 없는 기본 객체입니다. (예: 상품)
  • Composite (복합 객체): 자식 요소들을 리스트로 관리하며, Component의 메서드를 구현할 때 자식들에게 작업을 위임합니다. (예: 박스)

클래스 다이어그램 (Class Diagram)

Coposite Class Diagram

 

상세 클래스 정의

Namespace Implement

 

// 1. Component: 공통 인터페이스
public abstract class ItemComponent
{
    public abstract int GetPrice();
}
모든 클래스는 ItemComponent를 상속받아 GetPrice()라는 함수를 재정의 합니다. Client - 사용자는 ItemComponent의 GetPrice로 최종 계산 가격을 가져올 수 있는 구조를 따릅니다.

 

Namespace Leaf (개별 상품)
public class Product : ItemComponent
{
    private string name;
    private int price;

    public Product(string name, int price)
    {
        this.name = name;
        this.price = price;
    }

    public override int GetPrice()
    {
        return this.price;
    }
}
실제 개별 상품의 정의부, GetPrice()를 재정의하여, 값을 가져올 수 있게 정의 합니다.
Namespace Composite (박스) - 상품들의 복합체
public class Box : ItemComponent
{
    private List<ItemComponent> items = new List<ItemComponent>();

    public void Add(ItemComponent item)
    {
        items.Add(item);
    }

    public void Remove(ItemComponent item)
    {
        items.Remove(item);
    }

    // 핵심 로직: 자식들의 가격을 모두 더함 (재귀적 위임)
    public override int GetPrice()
    {
        int total = 0;
        foreach (var item in items)
        {
            total += item.GetPrice();
        }
        return total;
    }
}
Box (Compositor)는 각 개별 상품(Product)을 가지고, 마찬가지로 ItemComponent를 상속받아, GetPrice()를 오버라이드 하여, 값을 가져올 수 있는 구조를 따릅니다.

 

Namespace Client(User Side)

 

class Program
{
    static void Main(string[] args)
    {
        // 상품 생성 (Leaf)
        Product p1 = new Product("노트북", 1000);
        Product p2 = new Product("마우스", 50);
        Product p3 = new Product("충전기", 30);

        // 작은 박스 생성 및 상품 담기 (Composite)
        Box smallBox = new Box();
        smallBox.Add(p2);
        smallBox.Add(p3);

        // 큰 박스 생성 (상품 + 작은 박스 담기)
        Box bigBox = new Box();
        bigBox.Add(p1);      // 상품 추가
        bigBox.Add(smallBox); // 박스 안에 박스 추가

        // 클라이언트는 이것이 박스인지 상품인지 몰라도 가격 계산 가능
        Console.WriteLine($"총 주문 가격: {bigBox.GetPrice()}원"); 
        // 결과: 1000 + (50 + 30) = 1080원
    }
}
Client에서는 ItemComponent를 통해 정의된 GetPrice를 통해, 개별 상품의 값을 가져오거나, 복합체(Box)에 개별 상품들을 추가하여, 값을 가져올 수 있어, ItemComponent라는 하나의 interface를 통해 값을 가져온다는 처리를 진행하게 됩니다.