Composite Pattern 컴포지트 패턴
개념
객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만든 패턴이다.
이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체(Composite)를 똑같은 방법으로 다룰 수 있다.
예시
예를 들어, 펜케이크하우스메뉴 식당, 저녁메뉴 식당, 카페메뉴 식당을 모두 합쳐서 Client가 한번에 관리한다고 하자.
Client는 식당의 이름만 출력할 수도 있어야하고, 식당의 서브항목인 메뉴아이템도 모두 출력할 수 있어야하고, 특정 식당에 디저트라는 카테고리를 추가하여 그 디저트에 또 다시 여러가지 메뉴를 추가할 수 있어야 한다.
그리고 디저트메뉴의 하위항목들에 대해서만 반복작업을 한다든가 하는 식으로 유연하게 사용이 가능하여야 한다.
컴포지트 패턴 활용
이렇게 메뉴와 메뉴아이템이 트리 구조가 될 때 사용하면 좋은 것이 컴포지트 패턴이다.
(여기서 보라색의 Menu가 트리의 노드이고, 분홍색의 MenuItem이 트리의 잎이다.)
컴포지트 패턴을 이용하면 메뉴그룹과 메뉴 아이템을 똑같은 로직으로 처리할 수 있다. 메뉴와 메뉴 아이템을 같은 구조에 넣어서 부분-전체 계층 구조로 생성하는 것이다.
즉, 컴포지트 패턴을 이용하면 Client에서 바라볼 때 Menu와 MenuItem을 구분짓지않고 동일하게 접근하여 작업할 수 있다.
컴포지트 패턴 적용 방법
Client(Waitress)에서는 MenuComponent라는 인터페이스를 이용하여 MenuItem과 Menu에 모두 접근하게 된다.
MenuComponent는 Menu와 MenuItem 모두에 적용되는 인터페이스를 나타낸다.
MenuItem은 MenuComponent를 상속받지만, MenuItem에서 필요로 하는 메소드만 재정의하여 사용한다.
마찬가지로 Menu도 Menu에서 쓰일 법한 메소드만 오버라이드한다.
구현
// MenuComponent
public abstract class MenuComponent
{
// MenuComponent를 추가하고 제거하고 가져오기 위한 메소드
public void add(MenuComponent menuComponent)
{
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent)
{
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i)
{
throw new UnsupportedOperationException();
}
// MenuItem에서 필요한 메소드들
// (일부는 Menu에서도 사용됨)
public String getName()
{
throw new UnsupportedOperationException();
}
public String getDescription()
{
throw new UnsupportedOperationException();
}
public double getPrice()
{
throw new UnsupportedOperationException();
}
public boolean isVegetarian()
{
throw new UnsupportedOperationException();
}
// Menu와 MenuItem 모두에서 사용됨
public void print()
{
throw new UnsupportedOperationException();
}
}
MenuComponent 에서는 Menu(복합 객체)와 MenuItem(잎)에서 쓰일 모든 메소드를 구현한다.
모든 메소드에서 UnsupportedOperationException을 던지는 이유는 기본값은 예외를 던지도록 하고 필요한 객체에서 오버라이드하면 되기 때문이다. Menu와 MenuItem 두 개가 모두 MenuComponent를 상속받아서 사용하기 때문에 자기 역할에 맞지 않는 상황은 예외를 던지는 것을 기본으로 가져간다.
public class MenuItem extends MenuComponent
{
}
public class Menu extends MenuComponent
{
ArrayList
public void print()
{
System.out.print("\n" + getName());
System.out.println(", " + getDescription());
System.out.println("--------------------");
// recursive
Iterator iterator = menuComponents.iterator();
while(iterator.hasNext())
{
MenuComponent menuComponent = (MenuComponent)iterator.next();
menuComponent.print();
}
}
}
Menu의 print메소드는 특별하다. 이 부분을 보면 Menu를 재귀함수라고 일컬을 수 있다.
Menu가 가지고 있는 하위 항목인 MenuItem의 메소드 print를 호출하여 모든 메뉴를 순차적으로 출력하고 있다.
만약 하위 항목에 또 MenuItem 을 가지고 있다면 계속 하위 카테고리를 타고 내려가서 출력한다.
DFS(Depth First Search)를 떠올리면 이해가 더 쉬워질 것이다.