[헤드퍼스트 디자인패턴] 08. 반복자 패턴 (Iterator Pattern) 과
컴포지트 패턴 (Composite Pattern)
1. 반복자 패턴 (Iterator Pattern)
1.1 반복자 패턴이란?
컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공한다.
1.2 요구사항
- 팬케이크 하우스에서 파는 아침 메뉴와 객체 마을에서 파는 점심메뉴를 한곳에서 제공한다.
- 팬케이크 하우스에서는 메뉴를 List로 관리하고 객체 마을에서는 메뉴를 Array로 관리한다.
1.3 자격 요건 구현하기: 1차 시도
pancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();
DinerMenu dinerMenu = new DinerMenu();
MenuItem[] lunchItems = dinerMenu.getMenuItems();
for(int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
System.out.println(menuItem.getName());
System.out.println(menuItem.getPrice());
System.out.println(menuItem.getDescription());
}
for(int i = 0; i < lunchItems.size(); i++) {
MenuItem menuItem = lunchItems[i];
System.out.println(menuItem.getName());
System.out.println(menuItem.getPrice());
System.out.println(menuItem.getDescription());
}
문제점 : 다른 메소드도 위에 있는 코드와 비슷한 식으로 작성해야 한다. 두 메뉴를 사용하려면 항상 두개의 순환문을 사용해야 하고 만약 다른 구현법을 사용하는 레스토랑과 또 합병한다면 3개의 순환문이 필요하게 될 것이다.
1.4 반복자 패턴을 사용해서 반복을 캡슐화 하기
public class DinerMenuIterator implements Iterator<MenuItem> {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
public MenuItem next() {
// MenuItem이 List일 경우
// MenuItem menuItem = item.get(position);
MenuItem menuItem = item[position];
position = position + 1;
return menuItem;
}
public boolean hasNext() {
if(position >= items.length || items[position] == null {
return false;
} else {
return true;
}
}
public void remove() {
throw new UnsupportedOperationException("지울 수 없습니다");
}
}
위처럼 Iterator를 상속받아 자료구조에 맞게 next()와 hasNext()를 구현하면 클라이언트에서 집합체가 어떤 식으로 일이 처리되는지 전혀 모르는 상태에서 그 안에 들어있는 모든 항목을 대상으로 반복 작업을 수행할 수 있다.
private void printMenu(Iterator iterator) {
while(iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName());
System.out.print(menuItem.getPrice());
System.out.print(menuItem.getDescrption());
}
}
-> 자료구조가 어떤 것인지 알 필요 없이 hasNext()와 next()를 사용해 작업을 수행 할 수 있다.
2. 컴포지트 패턴 (Composite Pattern)
2.1 컴포지트 패턴이란?
객체를 트리구조로 구성해서 부분-전체 계층 구조를 구현한다. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있다.
2.2 요구사항
현재 가게에는 팬케이크 하우스 메뉴와 객체마을 식당 메뉴가 있다. 그런데 객체 마을 식당 메뉴에 디저트 서브메뉴를 추가하고 싶다. 객체마을 식당 메뉴에 서브메뉴가 들어갈 수 있으면 좋겠지만 형식이 다르므로 서브메뉴를 넣을 수 없다.
- 메뉴, 서브메뉴, 메뉴 항목 등을 모두 넣을 수 있는 트리 형태의 구조가 필요하다.
- 각 메뉴에 있는 모든 항목을 대상으로 특정 작업을 할 수 있는 방법을 제공해야 한다.
- 더 유연한 방법으로 아이템을 대상으로 반복 작업을 수행할 수 있어야 한다.
2.3 리팩토링 준비하기
메뉴, 메뉴 안에 들어있는 서브메뉴, 메뉴 항목을 모두 표현하려면 트리 구조가 가장 적합하다.
- MenuConponent 아래에 메뉴 항목이 있을 수도 있고, 서브 메뉴가 있을 수도 있다.
- MenuConponent는 MenuItem과 Menu 모두에게 적용되는 인터페이스이다.
- MenuItem과 Menu에서는 각각 필요한 메소드만 오버라이드 하고 나머지는 기본구현을 그대로 사용한다.
2.4 메뉴 구성 요소 구현하기
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과 Menu는 자기 역할에 맞지 않는 메소드는 예외를 던지는 코드를 자동으로 상속받게 한다.
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.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
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 description;
}
public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDescription());
System.out.println("---------------------");
// 메뉴의 아이템들의 print()를 호출하면 알아서 메뉴 아이템들이 출력 된다.
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = (MenuComponent) iterator.next();
menuComponent.print();
}
}
}
2.5 고려할 점
컴포지트 패턴은 한 클래스에서 복합객체와 잎의 두가지 역할을 해 단일 책임의 원칙을 위배한다. 그러나 어떤 원소가 복합 객체인지 잎인지 클라이언트가 알수 있어 투명성(transparency)을 확보할 수 있다. 이는 상황에 따라 원칙을 적절하게 사용해야 함을 보여주는 대표 사례이다.
'스터디 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[헤드퍼스트 디자인패턴] 10. 프록시 패턴 (Proxy pattern) (0) | 2022.08.21 |
---|---|
[헤드퍼스트 디자인패턴] 09. 상태 패턴(State Pattern) (0) | 2022.08.07 |
[헤드퍼스트 디자인패턴] 08. 템플릿 메소드 패턴(Template method) (0) | 2022.07.24 |
[헤드퍼스트 디자인패턴] 07. 어댑터 패턴(Adapter Pattern)과 퍼사드 패턴(Facade Pattern) (0) | 2022.07.17 |
[헤드퍼스트 디자인패턴] 06. 커맨드 패턴(Command Pattern) (0) | 2022.07.10 |
댓글