
복합체 패턴은 복합 객체와 단일 객체를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는 구조 패턴이다. 복합체 패턴은 전체-부분의 관계를 갖는 객체들 사이의 관계를 트리 계층 구조로 정의해야 할 때 유용하다. 윈도우나 리눅스의 파일 시스템 구조를 떠올려보면 쉽게 이해할 수 있다.
폴더 안에는 파일이 있을 수 있고 폴더도 들어있을 수 있다. 이를 복합적으로 담을 수 있다 해서 Composite 객체라고 불린다. 반면 파일은 단일 객체이기 때문에 이를 Leaf 객체라고 불린다. 즉, Leaf는 자식이 없다.

복합체 패턴은 바로 이 폴더와 파일을 동일한 타입으로 취급하여 구현을 단순화 시키는 것이 목적이다. 폴더 안에는 파일 뿐만 아니라 서브 폴더가 올 수 있고 또 서브 폴더 안에 서브 폴더가 오고 이런 식으로 계층 구조를 구현하다 보면, 자칫 복잡해 질 수 있는 복합 객체를 재귀 동작을 통해 하위 객체들에게 작업을 위임한다. 그러면 복합 객체와 단일 객체를 대상으로 똑같은 작업을 적용할 수 있어 단일 / 복합 객체를 구분할 필요가 거의 없어진다.
정리하자면, Composite 패턴은 그릇과 내용물을 동일시해서 재귀적인 구조를 만들기 위한 디자인 패턴이라 할 수 있다.

Component : Leaf와 Compsite 를 묶는 공통적인 상위 인터페이스Composite : 복합 객체로서, Leaf 역할이나 Composite 역할을 넣어 관리하는 역할을 한다. Leaf : 단일 객체로서, 단순하게 내용물을 표시하는 역할을 한다.Client : 클라이언트는 Component를 참조하여 단일 / 복합 객체를 하나의 객체로서 다룬다.interface Component {
void operation();
}
class Leaf implements Component {
@Override
public void operation() {
System.out.println(this + " 호출");
}
}
class Composite implements Component {
// Leaf 와 Composite 객체 모두를 저장하여 관리하는 내부 리스트
List<Component> components = new ArrayList<>();
public void add(Component c) {
components.add(c); // 리스트 추가
}
public void remove(Component c) {
components.remove(c); // 리스트 삭제
}
@Override
public void operation() {
System.out.println(this + " 호출");
// 내부 리스트를 순회하여, 단일 Leaf이면 값을 출력하고,
// 또다른 서브 복합 객체이면, 다시 그 내부를 순회하는 재귀 함수 동작이 된다.
for (Component component : components) {
component.operation(); // 자기 자신을 호출(재귀)
}
}
public List<Component> getChild() {
return components;
}
}
class Client {
public static void main(String[] args) {
// 1. 최상위 복합체 생성
Composite composite1 = new Composite();
// 2. 최상위 복합체에 저장할 Leaf와 또다른 서브 복합체 생성
Leaf leaf1 = new Leaf();
Composite composite2 = new Composite();
// 3. 최상위 복합체에 개체들을 등록
composite1.add(leaf1);
composite1.add(composite2);
// 4. 서브 복합체에 저장할 Leaf 생성
Leaf leaf2 = new Leaf();
Leaf leaf3 = new Leaf();
Leaf leaf4 = new Leaf();
// 5. 서브 복합체에 개체들을 등록
composite2.add(leaf2);
composite2.add(leaf3);
composite2.add(leaf4);
// 6. 최상위 복합체의 모든 자식 노드들을 출력
composite1.operation();
}
}

operation 메서드를 호출하게 되면, 단일체일 경우 값이 호출 되고, 복합체일 경우 자기 자신을 호출하는 재귀 함수에 의해 저장하고 있는 하위 Leaf 객체들을 순회하여 호출하게 된다.
복합 객체와 단일 객체를 상자와 아이템으로 비유해보자.

item 클래스와 이를 담든 Bag 클래스가 있다고 하자. 가방 안에 아이템을 담는 형식이니 Item 클래스는 Leaf가 되고 Bag 클래스는 Compostie가 된다.
우리가 구현하고 싶은 것은 Bag 속 리스트에 담긴 Item 객체들의 가격(price) 값을 추출하고 싶다고 한다. 그런데 단순히 가방 안에 아이템들이 들어있을 뿐 아니라 복수의 아이템을 담은 또다른 가방이 여러 개 들어있을 수 있다. 이러한 계층 트리 구조를 컴포지트 패턴으로 클래스를 구성을 하면 아래와 같이 되게 된다.
getPrice() 메서드는 Item일 경우 그대로 반환하고, Bag일 경우 자기 자신을 호출하여 가방에 있어있는 아이템을 순회하는 재귀 동작을 실행한다.// Component 인터페이스
interface ItemComponent {
int getPrice();
string getName();
}
// Composite 객체
class Bag : ItemComponent {
// 아이템과 가방을 모두 저장하기 위해 인터페이스 타입 리스트로 관리
List<ItemComponent> components = new List<ItemComponent>();
string name;
public Bag(string name) {
this.name = name;
}
// 리스트에 아이템 & 가방 추가
public void add(ItemComponent item) {
components.Add(item);
}
// 현재 가방 내용물 반환
public List<ItemComponent> getComponents() {
return components;
}
public int getPrice() {
int sum = 0;
foreach (ItemComponent component in components) {
// 요소가 Bag이면 알아서 '재귀함수' 동작, item이면 값을 반환 받음
sum += component.getPrice();
}
return sum;
}
public string getName() {
return name;
}
}
class Item_Composite : ItemComponent {
string name;
int price;
public Item_Composite(string name, int price) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public string getName() {
return name;
}
}
public class Composite : MonoBehaviour {
public void Start() {
// 1. 메인 가방 인스턴스 생성
Bag bag_main = new Bag("메인 가방");
// 2. 아이템 인스턴스 생성
Item_Composite armor = new Item_Composite("갑옷", 250);
Item_Composite sword = new Item_Composite("장검", 500);
// 3. 메인 가방에는 모험에 필요한 무구 아이템만을 추가
bag_main.add(armor);
bag_main.add(sword);
// 4. 서브 가방 인스턴스 생성
Bag bag_food = new Bag("음식 가방");
// 5. 아이템 인스턴스 생성
Item_Composite apple = new Item_Composite("사과", 290);
Item_Composite Banana = new Item_Composite("바나나", 160);
// 6. 서브 가방에 음식 추가
bag_food.add(apple);
bag_food.add(Banana);
// 7. 메인 가방에 서브 가방 추가
bag_main.add(bag_food);
//----------------------------------------------------//
// 가방 안에 있는 모든 아이템의 값어치 출력 ( 서브 가방에 있는 물건의 값어치 포함 )
printPrice(bag_main);
// 가방 안에 있는 모든 아이템의 값어치 출력
printPrice(bag_food);
}
public void printPrice(ItemComponent bag) {
int result = bag.getPrice();
Debug.Log(bag.getName() + "의 아이템 총합 : " + result + " 골드");
}
}

ItemComponent 만을 사용해서 price를 출력하기 때문에 Item, Bag 구현체 상관 없이 구현 메서드만 호출하면 내부에서 정의된 구현에 따라 원하는 값을 얻을 수 있게 된다.
만일 패턴을 사용하지 않고 계층 트리 구조를 선회하기 위해서는 매우 하드한 코딩을 해야 할지도 모른다.