저번 Iterator pattern에 이어 이번엔 컴포지트 패턴(Composite Pattern)이다.
맞다. 다른 패턴으로 문제를 푸는 방법에 대해 알아보려고 한다. Iterator도 문제법 중 하나이지만, 이제 이야기할 '컴포넌트 패턴'도 또다른 문제를 푸는 하나의 방법이다.
복합 패턴은 부분 - 전체 계층을 표현하기 위해 트리 구조로 개체를 구성 할 수 있습니다. 복합 클라이언트가 균일 개별 개체와 개체의 조성물을 처리 할 수 있습니다.
The composite pattern allows you to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
설명이 어렵다...
쉽게 이야기를 한 설명을 참조하면, '객체들의 관계를 트리 구조로 구성해 부분-전체 계층을 표현하는 패턴으로 사용자가 단일객체와 복합객체 모두 동일하게 다루도록 한다' 이다.
먼저 전체 구조를 보자.
Component는 interface로 모든 객체들은 이 Component 를 상속받는다. (Composite와 Leaf 둘다)
Leaf는 위 설명에서 이야기하는 부분 계층(균일 개별개체)이다. 즉 Leaf는 또다른 하위 구조를 가지지 않고 Leaf 객체 하나를 의미한다.
Composite(복합) 클래스는 자식으로 컴포넌트들을 가지고 있다.
이것과 iterators와는 어떤 관계인가?
우리는 새로운 시도를 하고 있다는 것을 생각해라. 이전 포스팅에서 했던 이터레이터와는 조금 다른 또다른 솔루션이다. 이제 곧 이야기하는 부분에서 iterators와 어떤 관계를 가지고 있는지 이야기를 할 것이다.
Composite를 이용해 Menu들을 재구성해보자.
먼저, Component Interface를 구현한다. 이 interface는 모든 메뉴들이 이를 implement해서 사용한다. 그런 뒤 Component의 메서드들을 가진 Leaf와 composite 클래스를 만든다.
MenuComponent는 MenuItem과 Menu의 interface다. abstract클래스로 사용해 MenuComponent에 있는 메서드들을 하위에서 사용할 수 있다. MenuItem은 Leaf클래스로 MenuComponent의 메소드들을 Override해서 사용할 수 있다. Menu 클래스는 컴포넌트 클래스로 역시 MenuComponent 클래스의 메서드들을 사용할 수 있지만 MenuItem과의 차이점은, menuComponents를 가지고 있다는 점이다.
public abstract class MenuComponent {
public void add(MenuComponent menuComponenet){
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}
public String getName(){
throw new UnsupportedOperationException();
}
public String getDescription(){
throw new UnsupportedOperationException();
}
public double getPrice(){
throw new UnsupportedOperationException();
}
public boolean isVegeterian(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
MenuItem을 Implement 해보자!
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;
public MenuItem(String name, String description, boolean vegetarian, double price){
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
public double getPrice(){
return price;
}
public boolean isVegetarian(){
return vegetarian;
}
public void print(){
System.out.print(" "+getName());
if(isVegetarian())
System.out.print("(v)");
System.out.print(" , "+getPrice());
System.out.print(" -- "+getDescription());
}
}
이번엔 composite Menu이다.
public class Menu extends MenuComponent {
ArrayList menuComponents = new ArrayList();
String name;
String description;
public Menu(String name, String description){
this.name = name;
this.description = description;
}
public void add(MenuComponent menuComponent){
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent){
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i){
return (MenuComponent) menuComponents.get(i);
}
public String getName(){
return name;
}
public String getDescription(){
return descipriton;
}
public void print(){
System.out.print("/n"+getName());
System.out.print(", " + getDescription());
System.out.print("--------------");
}
}
Leaf와 Composite 클래스 둘다 print를 implementation했다. Iterators를 사용하면 print를 호출하는 순간 모든 Menu들을 print할 수 있다!
아래 코드와 같이 print() 함수를 바꿔보자.
public void print(){
System.out.print(getName());
System.out.print(", "+ getDescription());
System.out.print("-------------------");
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext())){
MenuComponent menuComponent = (MenuComponent) iterator.next();
menuComponent.print();
}
}
자 이를 이용해 Waitress를 만들어보자
public class Waitress {
MenuComponent allMenus;
public Waitress(menuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}
Test drive를 해보자!
public class MenuTestDrive {
public static void main(String arg[]){
MenuComponent pancakeHouseMenu = new Menu("PANCAKGE HOUSE MENU", "Breakfast");
MenuComponent dinerMenu= new Menu("DINER MENU", "Lunch");
MenuComponent cafeMenu= new Menu("CAFE MENU", "Dinner");
MenuComponent dessertMenu= new Menu("DESSERTE MENU", "Dessert of course!");
MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
allMenus.add(pancakeHouse);
allMenus.add(dinnerMenu);
allMenus.add(cafeMenu);
dinerMenu.add(new MenuItem("Pasta", "Spaghetti with Marianra Sauce, and a slice of sourdough bread", true, 3.89));
dinerMenu.add(dessertMenu);
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
이렇게 하면 보답 쉽게 Menu들을 추가하고 관리할 수 있다! 단일 tree와 복합 tree를 관리할 수 있다는 것이 바로 이와 같은 점 때문이다!
+ 흠... 이렇게 보면 컴포지트 패턴이라는 것이 이해는 가나, 이것을 설명하기 위해 앞선 설명이 참 복잡하다.
+ 가장 위에 MenuComponent가 있고, MenuComponent들을 가진 또다른 MenuComponent 서브클래스들이 있다. 이렇게 하면, 모든 컬렉션과 클래스들이 MenuComponent를 가지게 되므로, 공통되는 메소드들을 사용할 수 있다는 것이 핵심!
여기서 잠시 의문이 드는것은, iterator패턴 때 이야기를 했던 '단일 책임 원칙'에 컴포지트 패턴이 맞지 않다는 것 아니냐?
컴포지트 패턴이 "단일 책임 원칙"을 위배하는 것은 맞다. 하지만 단일원칙을 위배하면서 얻는 이점은 "투명성"이다. 여기서 이야기하는 투명성이란, 하나의 인터페이스를 가지고 단일객체 혹은 복합 트리구조 모두 다 이 인터페이스를 참조 하는 것을 이야기한다. 즉 Menu를 두 가지 책임으로 나누어서 관리를 하게 되면, 수정해야 할 부분이 많아지며 관리가 어려워 진다. 컴포지트 패턴을 사용함으로써 (두가지 책임을 하므로써) 안정성이 떨어지는 건 사실이다. 때문에 안정성과 투명성 둘 중 하나를 양자택일하는 것을 개발자의 몫이다.
다시 Iterator의 이야기를 기억해보자.
우리는 전 포스팅에서 Iterator를 사용했다. Iterator 안에 print()를 써서 만들었던 것을 기억한다. Component안에 Iterator를 사용해보자 (Composite패턴에서 menu들의 array를 관리하는 것을 우리는 기억한다. 이 곳에 iterator를 사용하려고 한다)
public class Menu extends MenuComponent {
public Iterator createIterator(){
reutrn new CompositeIterator(menuComponents.iterator());
}
}
이렇게 하면, Composite Iterator클래스를 만들어보자.
public class CompositeITerator implements Iterator {
Stack stack = new Stack();
public CompositeIterator(Iterator iterator){
stack.push(iterator);
}
public Object next(){
if(hasNext()) {
Iterator iterator = (Iterator) stack.peak();
MenuComponent component = (MenuComponent) iterator.next();
if(component instanceof Menu)
stack.push(componene.createIterator());
return component;
}else
return null;
}
public boolean hasNext() {
if(stack.empty())
return false;
else
Iterator iterator = (Iterator) stack.peek();
if(!iterator.hasNext())
{
stack.pop();
return hasNext();
}else
return true;
}
}
이렇게 하면, Waitress가 vegetarianMenu 를 print 할 때에는,
public void printVegetarianMenu() {
Iterator iterator = allMenus.createIterator();
while( iterator.hasNext() ){
MenuComponent menuComponent = (MenuComponent) iterator.next();
try{
if(menuComponent.isVegetarian()){
menuComponent.print();
}
}catch(UnsupportedOperationException e) {}
}
}
위와 같이 사용을 할 수 있다.
다시 한번 이터레이터와 컴포지트 패턴을 정리해보자.
이터레이터 패턴은, for 문을 쓰지 않고 반복문을 객체지향에 맞도록 만든 디자인 패턴이다. iterator를 쓰는 이유는, 다형성 때문이다 (ArrayList, HashMap등 어떤 컬렉션이라도 동일한방식으로 컬렉션 안에 있는 항목에 접근 할 수 있기 때문이다. 즉 다양한 컬렉션 타입에 대해 접근이 가능하다. )
자바의 모든 컬렉션은 iterator이 구현되어 있다.
ArrayList<String> array = new ArrayList<String>();
array.iterator();
컴포지트패턴은, '객체를 트리구조로 구성하고, 개별객체와 복합객체가 구성되며, 클라이언트에서는 두 객체를 동일하게 사용을 할 수 있다'. 부분 - 전체 구조로 프로그램을 만들어야 할 때 쓰이게 되며, 클라이언트에서 개별객체와 객체 트리를 사용할 때 동일한 구조로 구현되어 있기 때문에 사용에 유용하다.
'옛글 > 번역본' 카테고리의 다른 글
애자일 안드로이드 프로그래밍 - 2 기술에 대한 시각과 Agility (0) | 2014.11.25 |
---|---|
프로그래밍 Design Pattern 이해하기 - 10 스테이트 패턴 (0) | 2014.09.28 |
프로그래밍 Design Pattern 이해하기 - 8 템플릿메소드패턴 (5) | 2014.09.16 |
프로그래밍 Design Pattern 이해하기 - 7 어댑터패턴&파사드패턴 (2) | 2014.08.31 |
프로그래밍 Design Pattern 이해하기 - 6 커맨드 패턴 (0) | 2014.08.25 |