컴포짓 패턴
컴포짓 패턴은 서로 다른 타입 같지만 결국 같은 동작을 하는 객체들을 공통 인터페이스로 묶어 트리 구조로 다루는 패턴입니다.
택배 박스를 생각해보면 이해가 쉽습니다. 큰 박스 안에 작은 박스, 그 안에 또 물건. 전체 가격을 계산하려면 그냥 "가격 얼마야?"라고 물으면 됩니다. 박스든 망치든 상관없이 모두 가격이 있는 상품이니까요.
연습 문제 코드부터 보겠습니다. 아래 내용과 요구사항을 보고 직접 리팩토링 해보세요.
public class Hammer {
private int price;
public Hammer(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
public class SmallBox {
private int price;
private List<Hammer> hammers;
public SmallBox(int price, List<Hammer> hammers) {
this.price = price;
this.hammers = hammers == null ? new ArrayList<>() : hammers;
}
public int getTotal() {
int sum = price;
for (Hammer h : hammers) {
sum += h.getPrice();
}
return sum;
}
}
public class BigBox {
private int price;
private List<SmallBox> smallBoxes;
private List<Hammer> hammers;
public BigBox(int price, List<SmallBox> smallBoxes, List<Hammer> hammers) {
this.price = price;
this.smallBoxes = smallBoxes == null ? new ArrayList<>() : smallBoxes;
this.hammers = hammers == null ? new ArrayList<>() : hammers;
}
public int getTotal() {
int sum = price;
for (SmallBox sb : smallBoxes) {
sum += sb.getTotal();
}
for (Hammer h : hammers) {
sum += h.getPrice();
}
return sum;
}
}
public class Client {
public static void main(String[] args) {
SmallBox smallBox = new SmallBox(
5,
List.of(new Hammer(15))
);
BigBox bigBox = new BigBox(
10,
List.of(smallBox),
List.of(new Hammer(20))
);
System.out.println(bigBox.getTotal());
}
}
현재 코드의 문제점
현재 코드의 문제점은 새로운 제품이 추가될 때마다 모든 박스 클래스를 수정해야 한다는 점입니다. 예를 들어 Book이라는 제품이 추가되면 SmallBox와 BigBox에 각각 List<Book> books 필드를 추가하고, 생성자를 수정하고, getTotal() 메서드에 순회 로직을 추가해야 합니다.
또한 박스나 포장지가 추가되면 Hammer와 같은 물건들과 관련된 로직을 추가해야하는 문제가 있습니다. 현재는 Hammer 클래스만 있지만 MiddleBox와 IPhone GalaxyPhone 객체를 추가된다고 생각하면 눈앞이 깜깜해집니다.
이런 코드가 만들어지는 이유는 요구사항을 있는 그대로 코드로 옮겨담는 사고방식 때문입니다. 회사에서 다루는 상품은 망치하나 이며 망치가 많은 경우 큰박스에 넣어서 보낼 수도, 망치 수량이 적을 경우 작은 박스 하나에 넣어서 보낼 수도, 망치의 포장을 중요하게 여길 경우 큰 박스 안에 여러 작은 박스를 넣어서 택배를 보낼 수 도 있다. 라는 요구사항이 주어지면 위와 같은 안티패턴이 되곤 합니다.
하지만 잠깐 생각해보면 박스나 망치나 총 가격을 계산할 때는 포장재인지 상품인지 구별할 필요가 없습니다. 다만 박스는 물건을 담을 수 있고 망치는 담을 수 없다는 차이만 있을 뿐입니다. 모든 것은 물건이고 각 물건이 공통된 행동을 수행한다면 컴포짓 패턴 사용을 고려해볼 수 있습니다.
컴포짓 패턴의 구조

1. Component 인터페이스 = Product 인터페이스 생성 (공통 인터페이스)
2. Leaf 클래스 = Hammer는 그 안에 중첩 객체를 담을 필요가 없으므로 Leaf 클래스
3. Composite 클래스 = Box가 Product 인터페이스 구현 (Box는 Box안에 Box가 있을 수도 Hammer라는 Leaf를 담을 수도 있음)
요구사항
- Product 인터페이스는 모든 요소가 공통으로 가져야 할 동작(calculateTotalPrice)을 정의한다.
- Hammer 같은 Leaf는 자신의 가격만 반환한다.
- Box(Composite)는 List<Product>를 가지며, 자신의 가격 + 자식들의 가격 합을 반환한다.
- Box 안에 Box를 넣을 수 있어야 한다. (재귀적 구조)
- 클라이언트는 Product 타입으로 단일 상품이든 박스든 동일하게 다룰 수 있어야 한다.
정답
Product 인터페이스로 모든 요소의 공통 동작을 추상화하여, 클라이언트가 개별 상품과 박스를 구분하지 않고 동일하게 다룰 수 있게 만들었습니다.
SmallBox, BigBox로 분리되어 있던 중복 코드가 Box 하나로 통합되었습니다.
새로운 제품(Book)을 추가할 때 Product 인터페이스만 구현하면 되고, 기존 Box 클래스는 전혀 수정할 필요가 없습니다.
Box 안에 Box를 넣는 재귀적 구조가 자연스럽게 지원됩니다.
public interface Product {
int calculateTotalPrice();
}
public class Hammer implements Product {
private int price;
public Hammer(int price) {
this.price = price;
}
@Override
public int calculateTotalPrice() {
return price;
}
}
public class Box implements Product {
private int price;
private List<Product> products = new ArrayList<>();
public Box(int price) {
this.price = price;
}
public void add(Product product) {
products.add(product);
}
public void remove(Product product) {
products.remove(product);
}
@Override
public int calculateTotalPrice() {
int sum = price;
for (Product p : products) {
sum += p.calculateTotalPrice();
}
return sum;
}
}
public class Client {
public static void main(String[] args) {
// 작은 박스에 망치와 폰을 담음
Box smallBox = new Box(5);
smallBox.add(new Hammer(15));
// 큰 박스에 작은 박스와 망치를 담음
Box bigBox = new Box(10);
bigBox.add(smallBox); // 박스 안에 박스!
bigBox.add(new Hammer(20));
System.out.println(bigBox.calculateTotalPrice());
// 단일 상품도 동일한 방식으로 다룰 수 있음
Product hammer = new Hammer(30);
System.out.println(hammer.calculateTotalPrice());
}
}'개발지식 > Design Pattern' 카테고리의 다른 글
| 브릿지 패턴 - 개념과 연습문제 (0) | 2026.01.06 |
|---|---|
| 싱글톤 패턴을 구현하는 5가지 방법 (0) | 2026.01.06 |
| 어탭터 패턴 (객체 어댑터, 클래스 어댑터) - 개념과 연습문제 (0) | 2025.12.28 |
| 프로토타입 패턴 - 개념과 연습문제 (0) | 2025.12.24 |
| 빌더 패턴 - 개념과 연습문제 (0) | 2025.12.24 |