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

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

- Component : Leaf와 Compsite 를 묶는 공통적인 상위 인터페이스
- Composite : 복합 객체로서, Leaf 역할이나 Composite 역할을 넣어 관리하는 역할을 한다.
- Component 구현체들을 내부 리스트로 관리한다
- add 와 remove 메소드는 내부 리스트에 단일 / 복합 객체를 저장
- Component 인터페이스의 구현 메서드인 operation은 복합 객체에서 호출되면 재귀 하여, 추가 단일 객체를 저장한 하위 복합 객체를 순회하게 된다.
- Leaf: 단일 객체로서, 단순하게 내용물을 표시하는 역할을 한다.
- Component 인터페이스의 구현 메서드인 operation은 단일 객체에서 호출되면 적절한 값만 반환한다
- Client : 클라이언트는 Component를 참조하여 단일 / 복합 객체를 하나의 객체로서 다룬다.
패턴 흐름
// Component 인터페이스
interface ItemComponent {
int getPrice();
String getName();
}
// Composite 객체
class Bag implements ItemComponent {
// 아이템들과 서브 가방 모두를 저장하기 위해 인터페이스 타입 리스트로 관리
List<ItemComponent> components = new ArrayList<>();
String name; // 가방 이름
public Bag(String name) {
this.name = name;
}
// 리스트에 아이템 & 가방 추가
public void add(ItemComponent item) {
components.add(item);
}
// 현재 가방의 내용물을 반환
public List<ItemComponent> getComponents() {
return components;
}
@Override
public int getPrice() {
int sum = 0;
for (ItemComponent component : components) {
// 만일 리스트에서 가져온 요소가 Item이면 정수값을 받을 것이고, Bag이면 '재귀 함수' 동작이 되게 된다 ☆
sum += component.getPrice(); // 자기 자신 호출(재귀)
}
return sum; // 그렇게 재귀적으로 돌아 하위 아이템들의 값을 더하고 반환하게 된다.
}
@Override
public String getName() {
return name;
}
}
// Leaf 객체
class Item implements ItemComponent {
String name; // 아이템 이름
int price; // 아이템 가격
public Item(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public int getPrice() {
return price;
}
@Override
public String getName() {
return name;
}
}
class Client {
public static void main(String[] args) {
// 1. 메인 가방 인스턴스 생성
Bag bag_main = new Bag("메인 가방");
// 2. 아이템 인스턴스 생성
Item armor = new Item("갑옷", 250);
Item sword = new Item("장검", 500);
// 3. 메인 가방에는 모험에 필요한 무구 아이템만을 추가
bag_main.add(armor);
bag_main.add(sword);
// 4. 서브 가방 인스턴스 생성
Bag bag_food = new Bag("음식 가방");
// 5. 아이템 인스턴스 생성
Item apple = new Item("사과", 400);
Item banana = new Item("바나나", 130);
// 6. 서브 가방에는 음식 아이템만을 추가
bag_food.add(apple);
bag_food.add(banana);
// 7. 서브 가방을 메인 가방에 넣음
bag_main.add(bag_food);
// ----------------------------------------------------- //
Client client = new Client();
// 가방 안에 있는 모든 아이템의 총 값어치를 출력 (가방안에 아이템 뿐만 아니라 서브 가방도 들어있음)
client.printPrice(bag_main);
// 서브 가방 안에 있는 모든 아이템의 총 값어치를 출력
client.printPrice(bag_food);
}
public void printPrice(ItemComponent bag) {
int result = bag.getPrice();
System.out.println(bag.getName() + "의 아이템 총합 : " + result + " 골드");
}
}
패턴 특징
사용 시기
- 데이터를 다룰때 계층적 트리 표현을 다루어야 할때
- 복잡하고 난해한 단일 / 복합 객체 관계를 간편히 단순화하여 균일하게 처리하고 싶을때
장점
- 단일체와 복합체를 동일하게 여기기 때문에 묶어서 연산하거나 관리할 때 편리하다.
- 다형성 재귀를 통해 복잡한 트리 구조를 보다 편리하게 구성 할 수 있다.
- 수평적, 수직적 모든 방향으로 객체를 확장할 수 있다.
- 새로운 Leaf 클래스를 추가하더라도 클라이언트는 추상화된 인터페이스 만을 바라보기 때문에 개방 폐쇄 원칙(OCP)Visit Website을 준수 한다. (단일 부분의 확장이 용이)
단점
- 재귀 호출 특징 상 트리의 깊이(depth)가 깊어질 수록 디버깅에 어려움이 생긴다.
- 설계가 지나치게 범용성을 갖기 때문에 새로운 요소를 추가할 때 복합 객체에서 구성 요소에 제약을 갖기 힘들다.
- 계층형 구조에서 leaf 객체와 composite 객체들을 모두 동일한 인터페이스로 다루어야하는데, 이 공통 인터페이스 설계가 까다로울 수 있다.
- 복합 객체가 가지는 부분 객체의 종류를 제한할 필요가 있을 때
- 수평적 방향으로만 확장이 가능하도록 Leaf를 제한하는 Composite를 만들때