컴포지트 패턴
객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다.
이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있다.
- 이터레이터 패턴에서 구현한 예제에서 만약 점심, 저녁 메뉴에 디저트 메뉴들을 추가해야 한다고 가정해보자.
- 이럴 때 컴포지트 패턴을 구현한다면 다른 메뉴들을 추가하더라도 처음부터 구현할 필요 없이 간단하게 구현할 수 있다.
Component
- Component는 Composite와 Leaf의 부모가 되고 해당 두 객체가 가질 수 있는 동작들을 가지고 있다.
Composite
- Composite는 Component를 컬렉션으로 가지고 있다.
- 즉 자기 자신인 Composite와 Leaf를 가질 수 있는 것이다.
- 컬렉션으로 가지고 있는 Component들을 추가 제거할 수 있고 opertion() 또한 가능하다.
Leaf
- opertion()만 할 수 있는 객체이다.
예제
Component
public abstract class MenuComponent {
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
}
- MenuComponent가 Component가 된다.
- Composite와 Leaf가 가질 수 있는 모든 행동을 가진다.
- 만약 하위 클래스들이 이 행동을 구현하지 않는다면 예외를 던지도록 설계하였다.
Composite
public class Menu extends MenuComponent {
private List<MenuComponent> menuComponents;
private String name;
public Menu(String name) {
menuComponents = new ArrayList<>();
this.name = name;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public String getName() {
return name;
}
@Override
public void print() {
System.out.println("\n " + getName());
System.out.println("--------------------------------");
for (MenuComponent menuComponent : menuComponents) {
menuComponent.print();
}
}
}
- Menu가 Composite가 된다.
- MenuComponent를 List형태로 가지고 있고 menuComponents를 통해 행동을 정의하고 저장할 수 있다.
- Menu, MenuItem 모두 menuComponents에 추가될 수 있다.
Leaf
public class MenuItem extends MenuComponent {
private String name;
private String description;
private double price;
public MenuItem(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.print(getName());
System.out.println(" ," + getPrice());
System.out.println(" -- " + getDescription());
}
}
- MenuItem이 Leaf이다.
- 기존 타입의 데이터로 name, description, price만 존재한다.
- MenuItem은 오직 출력만 하는 기능을 제공할 뿐 이외의 기능은 없다.
Client
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent getAllMenus) {
this.allMenus = getAllMenus;
}
public void printMenu() {
allMenus.print();
}
}
- 클라이언트인 Waitress는 MenuComponent만을 참조하여 모든 메뉴들을 출력할 수 있다.
Test
public class Main {
public static void main(String[] args) {
MenuComponent lunchMenu = new Menu("점심 메뉴");
MenuComponent dinerMenu = new Menu("저녁 메뉴");
MenuComponent dessertMenu = new Menu("디저트 메뉴");
MenuComponent allMenus = new Menu("전체 메뉴");
createLunchAndDinerMenu(lunchMenu, dinerMenu);
allMenus.add(lunchMenu);
allMenus.add(dinerMenu);
createDesserMenu(dessertMenu);
lunchMenu.add(dessertMenu);
dinerMenu.add(dessertMenu);
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
private static void createLunchAndDinerMenu(MenuComponent lunchMenu, MenuComponent dinerMenu) {
lunchMenu.add(new MenuItem("토스트 세트"
, "기본 토스트에 아메리카노 포함"
, 4000));
lunchMenu.add(new MenuItem("아보카도 토스트 세트"
, "아보카도와 다양항 야채들이 들어간 샌드위치에 아메리카노 포함"
, 5000));
dinerMenu.add(new MenuItem("알리오 올리오 파스타"
, "베이컨, 마늘, 핫페퍼가 들어간 파스타"
, 10000));
dinerMenu.add(new MenuItem("마르게리타 피자"
, "토마토, 모차렐라, 바질이 들어간 피자"
, 12000));
}
private static void createDesserMenu(MenuComponent dessertMenu) {
dessertMenu.add(new MenuItem("팥빙수"
, "팥과 연유가 가득 들어간 빙수"
, 4000));
dessertMenu.add(new MenuItem("망고빙수"
, "망고가 들어간 빙수"
, 6000));
}
}
- 우선 각각 점심, 저녁, 디저트 메뉴를 생성하였다.
- 전체 메뉴를 생성하였다. 전체 메뉴가 Component가 될 것이다.
- 점심, 저녁 메뉴에 각 메뉴 아이템을 생성하여 추가한다.
- 전체 메뉴에 점심, 저녁 메뉴를 추가한다.
- 디저트 메뉴에 디저트 메뉴 아이템들을 추가한다.
- 점심, 저녁 메뉴에 디저트 메뉴를 추가한다.
- 웨이터는 Component인 allMenus 하나만 알고 있으면 모든 메뉴를 출력할 수 있다.
- 출력을 확인해보면 점심, 저녁 메뉴에 디저트 메뉴가 알맞게 들어간 것을 알 수 있다.
단일 역할 원칙
클래스를 바꾸는 이유는 한 가지뿐이어야 한다.
이터레이터 패턴에서 언급한 원칙이다.
- 컴포지트 패턴에서는 단일 역할 원칙이 지켜지지 않았다.
- 유연성 측면에서는 단일 역할 원칙을 지키지 않은 컴포지트 패턴이 안 좋다고 생각할 수 있다.
- 하지만 클라이언트인 웨이터는 복잡한 구현 없이 Component 하나만 알고 있으면 모든 메뉴를 출력할 수 있다.
- 이렇듯 디자인을 설계할 때는 기존의 디자인 원칙을 지키는 것도 중요하나 더 효율적인 방법이 있다면 그게 따라 새로운 방법을 시도해 나가야 한다.
- 모든 설계는 적절한 트레이드오프를 통해 이루어져야 한다.
'디자인 패턴' 카테고리의 다른 글
프록시 패턴(Proxy Pattern) (0) | 2019.12.20 |
---|---|
스테이트 패턴(State Pattern) (0) | 2019.12.20 |
이터레이터 패턴(Iterator Pattern) (0) | 2019.12.17 |
템플릿 메서드 패턴(Template Method Pattern) (0) | 2019.12.17 |
퍼사드 패턴(Facade Pattern), 최소 지식 원칙 (0) | 2019.12.16 |