본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 08. 반복자 패턴 (Iterator Pattern) 과 컴포지트 패턴 (Composite Pattern)

by 디토20 2022. 7. 31.
반응형

 

 

 

 

[헤드퍼스트 디자인패턴] 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)을 확보할 수 있다. 이는 상황에 따라 원칙을 적절하게 사용해야 함을 보여주는 대표 사례이다.

728x90
반응형

댓글