差分
このページの2つのバージョン間の差分を表示します。
| 次のリビジョン | 前のリビジョン | ||
| study:java:design_pattern:composite [2008/09/14 06:41] – created banana | study:java:design_pattern:composite [2008/09/14 09:08] (現在) – banana | ||
|---|---|---|---|
| 行 24: | 行 24: | ||
| 이제 컴포지트 패턴의 클래스 다이어그램을 살표볼 차례입니다. | 이제 컴포지트 패턴의 클래스 다이어그램을 살표볼 차례입니다. | ||
| + | |||
| + | |||
| + | |||
| + | |||
| ===== Class Diagram ===== | ===== Class Diagram ===== | ||
| {{: | {{: | ||
| + | 컴포지트 패턴을 메뉴에 어떻게 적용할 수 있을까요? | ||
| + | 됩니다. 이 인터페이스는 메뉴와 메뉴 항목 모두에 적용되는 공통 인터페이스 역할을 하며, 이 인터페이스가 | ||
| + | 있어야만 그 둘을 똑같은 방법으로 처리할 수 있습니다. 즉, 메뉴와 메뉴 항목에 대해서 같은 메소드를 호출 | ||
| + | 할 수 있게 되죠. | ||
| + | |||
| + | 사실 메소드 중에는 메뉴 항목에 대해서 호출하는게 말이 안되는 것도 있을 것이고, 메뉴에 대해 호출하면 이상한 | ||
| + | 메소드도 있을 것입니다. 하지만 그런 문제를 해결하는 방법은 잠시 후에 알아보도록 하겠습니다. 일단 지금은 | ||
| + | 메뉴를 어떤 식으로 컴포지트 패턴에 끼워 맞출 수 있을지 생각해 봅시다. | ||
| + | |||
| + | {{: | ||
| + | |||
| + | |||
| + | ===== MenuComponent ===== | ||
| + | 이제 **%%MenuComponent%%** 추상 클래스부터 시작해 봅시다. 메뉴 구성요소는 잎 노드와 복합노드 모두에서 | ||
| + | 쓰이는 인터페이스 역할을 한다는 점을 꼭 기억해 둡시다. 어쩌면 "이 **%%MenuComponent%%**에서 두가지 | ||
| + | 역할을 맡고 있는 것이 아닌가?" | ||
| + | 대해서는 나중에 다시 생각해 보도록 하겠습니다. 일단 지금은 %%MenuItem%%(잎)이나 Menu(복합 객체)에서 각자 | ||
| + | 용도에 맞지 않아서 구현할 필요가 없는 메소드에 대해서는 그냥 기본 메소드를 그대로 쓸 수 있도록 기본 구현 | ||
| + | 을 만들어 보도록 하겠습니다. | ||
| + | |||
| + | <code java> | ||
| + | public abstract class 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(); | ||
| + | } | ||
| + | |||
| + | public String getName() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | public String getDescription() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | public double getPrice() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | public boolean isVegetarian(){ | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | |||
| + | public void print() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ===== MenuItem Class ===== | ||
| + | 이제 **%%MenuItem%%** 클래스를 만들어 봅시다. 이 클래스는 컴포지트 패턴 다이어그램에서 앞에 해당하는 | ||
| + | 클래스라는 점, 그리고 복합 객체의 원소에 해당하는 행동을 구현해야 한다는 점을 잘 기억해 둡시다. | ||
| + | |||
| + | <code java> | ||
| + | public class MenuItem extends MenuComponent { | ||
| + | String name; | ||
| + | String description; | ||
| + | boolean vegetarian; | ||
| + | double price; | ||
| + | |||
| + | public MenuItem(String name, String description, | ||
| + | this.name = name; | ||
| + | this.description = description; | ||
| + | this.vegetarian = vegetarian; | ||
| + | this.price = price; | ||
| + | } | ||
| + | |||
| + | public Iterator createIterator() { | ||
| + | return new NullIterator(); | ||
| + | } | ||
| + | |||
| + | 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(" | ||
| + | if(isVegetarian()) { | ||
| + | System.out.print(" | ||
| + | } | ||
| + | System.out.println(", | ||
| + | System.out.println(" | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | 복합 객체에 대한 반복자를 구현하기 위해서는 모든 구성요소에 %%createIterator()%%메소드 | ||
| + | 를 추가해야 합니다. 잉? 그런데 %%NullIterator%%는 도대체 뭘까요? 잠시 후에 알아보도록 하겠습니다. | ||
| + | |||
| + | |||
| + | ===== Menu Class ===== | ||
| + | **%%MenuItem%%**도 다 준비됐으니 이제 복합 객체 클래스인 **%%Menu%%**만 준비하면 됩니다. 복합 객체 클래스에는 | ||
| + | %%MenuItem%%는 물론 다른 Menu도 저장할 수 있습니다. %%MenuComponent%%에 있는 메소드 가운데 getPrice()와 | ||
| + | isVegetarian()메뉴에서는 별 의미가 없기 때문에 구현하지 않습니다. | ||
| + | |||
| + | <code java> | ||
| + | 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 Iterator createIterator(){ | ||
| + | return new CompositeIterator(menuComponents.iterator()); | ||
| + | } | ||
| + | |||
| + | 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 getDescription() { | ||
| + | return description; | ||
| + | } | ||
| + | |||
| + | public void print() { | ||
| + | System.out.print(" | ||
| + | System.out.println(", | ||
| + | System.out.println(" | ||
| + | |||
| + | Iterator iterator = menuComponents.iterator(); | ||
| + | while (iterator.hasNext()) { | ||
| + | MenuComponent menuComponent = (MenuComponent) iterator.next(); | ||
| + | menuComponent.print(); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | %%getPrice()%%와 %%isVegetarian()%%메소드는 Menu에는 어울리지 않는 메소드이므로 그냥 구현하지 않습니다. | ||
| + | (어쩌면 %%isVegetarian%%은 메뉴에도 적용할 수 있을 것 같지만, 여기에서는 그렇게 하지 않겠습니다.) Menu에 | ||
| + | 대해서 이 메소드를 호출하면 %%UnsupportedOperationException%%이 던져질 것입니다. | ||
| + | |||
| + | print()메소드를 살펴보면 Iterator패턴이 사용되었음을 알 수 있습니다. 반복작업을 수행하는 중에 다른 메뉴가 | ||
| + | 나타난다면 그 메뉴에서 또 다른 반복잡업을 실행하게 됩니다. 서브 메뉴가 여러 단계로 중첩되어 있으면 그런 과정이 | ||
| + | 여러번 반복되겠죠. | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ===== CompositeIterator Class ===== | ||
| + | **%%CompositeIterator%%**는 중책을 맡고 있는 반복자입니다. 복합 객체 안에 들어있는 | ||
| + | %%MenuItem%%에 대해 반복작업을 할 수 있게 해 주는 기능을 제공하죠. 모든 자식 메뉴들 및 자식의 자식 | ||
| + | 등도 빠짐없이 챙겨야 합니다. | ||
| + | |||
| + | 코드는 다음과 같습니다. 코드 자체는 그리 길지 않지만 조금 이해하기 힘들 수도 있습니다. 계속 머릿속으로 | ||
| + | " | ||
| + | |||
| + | <code java> | ||
| + | import java.util.* | ||
| + | |||
| + | 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.peek(); | ||
| + | MenuComponent component = (MenuComponent) iterator.next(); | ||
| + | if (component istanceof Menu) { | ||
| + | stack.push(component.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; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public void remove() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | ===== NullIterator Class ===== | ||
| + | 이제 널 반복자(Null Iterator)가 왜 필요한지 알아봐야 할 때가 되었군요. %%MenuItem%%에 대해서 생각해 보변, | ||
| + | 반복작업을 할 대상이 없다는 것을 알 수 있습니다. 상황이 그렇다 보니 %%createIterator()%%메소드를 구현하기가 | ||
| + | 애매해집니다. 이런 경우에 두가지 방법을 떠올릴 수 있을 것입니다. | ||
| + | |||
| + | **첫 번째 방법:**\\ | ||
| + | 널을 리턴한다. | ||
| + | |||
| + | createIterator()에서 그냥 널을 리턴할 수도 있을 것입니다. 하지만 그렇게 하면 클라이언트에서 | ||
| + | 리턴된 값이 널인지 아닌지를 판단하기 위한 조건문을 써야 한다는 단점이 있죠. | ||
| + | |||
| + | **두 번째 방법: **\\ | ||
| + | %%hasNext()%%가 호출되었을 때 무조건 false를 리턴하는 반복자를 리턴한다. | ||
| + | |||
| + | 이 방법이 좀 나아 보이는군요. 이렇게 하면 여전히 반복자를 리턴할 수 있기 때문에 | ||
| + | 클라이언트에서는 리턴된 객체가 널 객체인지에 대해 신경 쓸 필요가 없습니다. " | ||
| + | 반복자를 만든다고 생각하면 됩니다. | ||
| + | |||
| + | 두 번째 방법이 확실히 좋아 보이는군요. 아무 일도 하지 않는 반복자를 %%NullIterator%%라고 부르고, | ||
| + | | ||
| + | |||
| + | <code java> | ||
| + | import java.util.Iterator; | ||
| + | |||
| + | public class NullIterator implements Iterator { | ||
| + | |||
| + | public Object next() { | ||
| + | return null; | ||
| + | } | ||
| + | |||
| + | public boolean hasNext() { | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | public void remove() { | ||
| + | throw new UnsupportedOperationException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== Waitress Class ===== | ||
| + | 여기까지 오는데 여정이 길었습니다. 이제 마지막으로 Client인 **%%Waitress%%**클래스를 구현할 차례입니다. | ||
| + | 앞서 메뉴에 있는 모든 항목에 대해서 반복작업을 수행할 수 있는 방법이 생겼으므로 그 방법을 활용해서 | ||
| + | **%%Waitress%%**에 어떤 항목이 채식주의자용 메뉴인지 알아내기 위한 메소드를 추가해보도록 하겠습니다. | ||
| + | |||
| + | <code java> | ||
| + | public class Waitress { | ||
| + | MenuComponent allMenus; | ||
| + | |||
| + | public Waitress(MenuComponent allMenus) { | ||
| + | this.allMenus = allMenus; | ||
| + | } | ||
| + | |||
| + | public void printMenu() { | ||
| + | allMenus.print(); | ||
| + | } | ||
| + | |||
| + | public void printVegetarianMenu() { | ||
| + | Iterator iterator = allMenus.createIterator(); | ||
| + | System.out.println(" | ||
| + | while (iterator.hasNext()) { | ||
| + | MenuComponent menuComponent = (MenuComponent) iterator.next(); | ||
| + | try { | ||
| + | if (menuComponent.isVegetarian()) { | ||
| + | menuComponent.print(); | ||
| + | } | ||
| + | } catch (UnsupportedOperationException e) {} | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | %%Menu%%의 %%isVegetarian()%%에서는 항상 예외를 던지도록 만들어 놨습니다. 그래서 예외가 발생하면 그 예외를 | ||
| + | 잡긴 하지만, 아무 일 없이 반복 작업을 계속 수행하도록 했습니다. | ||