Iterator Pattern 정리

테사벨로그·2025년 10월 23일

Design Pattern

목록 보기
11/19

1. 왜 Iterator Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: 서로 다른 자료구조를 사용하는 메뉴들
public class PancakeHouseMenu {
    ArrayList<MenuItem> menuItems;  // ArrayList 사용
}

public class DinerMenu {
    MenuItem[] menuItems;  // 배열 사용
}

// 메뉴 출력 코드
ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();
MenuItem[] lunchItems = dinerMenu.getMenuItems();

// 각각 다른 방식으로 순회해야 함
for (int i = 0; i < breakfastItems.size(); i++) {
    MenuItem item = (MenuItem)breakfastItems.get(i);
}

for (int i = 0; i < lunchItems.length; i++) {
    MenuItem item = lunchItems[i];
}

문제점:

  • 구체적인 구현(ArrayList, Array)에 직접 의존
  • 내부 자료구조가 변경되면 클라이언트 코드도 수정 필요
  • 각 컬렉션마다 다른 순회 방법을 알아야 함
  • 코드 중복 발생

2. Iterator Interface VS Aggregate Interface

1. Iterator Interface

  • "컬렉션의 요소들을 순회하는 방법"
  • 순회 동작의 규격만 정의
  • "can-traverse" 관계 (순회할 수 있음)
public interface Iterator {
    boolean hasNext();  // 다음 요소가 있는지 확인
    Object next();      // 다음 요소 반환
}

왜 Interface인가?

  • 배열, ArrayList, LinkedList 등 다양한 자료구조를 순회할 수 있음
  • 순회 방식(전진, 후진, 건너뛰기 등)이 다양할 수 있음
  • 클라이언트는 구체적인 순회 방법을 몰라도 됨

2. Aggregate(Menu) Interface

  • "나는 Iterator를 제공할 수 있어요"
  • Iterator 생성의 규격만 정의
  • "can-provide" 관계 (Iterator를 제공할 수 있음)
public interface Menu {
    Iterator createIterator();  // Iterator 생성
}

왜 Interface인가?

  • 다양한 메뉴(아침 메뉴, 점심 메뉴, 저녁 메뉴)가 존재
  • 각 메뉴는 내부적으로 다른 자료구조 사용 가능
  • 클라이언트는 내부 구조를 알 필요 없이 Iterator만 받으면 됨

3. 왜 Abstract Class가 아닌 Interface인가?

Interface를 사용하는 이유

  1. 느슨한 결합 (Loose Coupling)

    // ✅ 클라이언트는 인터페이스만 의존
    public class Waitress {
        Menu menu;  // 구체적인 메뉴가 아닌 인터페이스에 의존
        
        public void printMenu() {
            Iterator iterator = menu.createIterator();
            while (iterator.hasNext()) {
                MenuItem item = (MenuItem) iterator.next();
            }
        }
    }
  2. 다중 구현 가능

    // ✅ 한 클래스가 Menu이면서 다른 역할도 가능
    public class CafeMenu implements Menu, Serializable {
        // Menu 기능과 직렬화 기능 동시 제공
    }
  3. 단일 책임 원칙 (Single Responsibility)

    • Aggregate: 데이터 저장 책임
    • Iterator: 데이터 순회 책임
    • 두 책임을 분리하여 각자의 변경 이유가 명확함

4. Iterator Pattern 핵심 구조

      Menu (Aggregate)  ────────> Iterator
   (컬렉션을 가진 객체)        (순회 담당 객체)
   
   - createIterator() 제공
   - 내부 자료구조 캡슐화
   - Iterator를 통해서만 접근 허용

핵심 개념:

  • 내부 구조 숨김: 클라이언트는 ArrayList인지 Array인지 몰라도 됨
  • 통일된 인터페이스: 모든 컬렉션을 동일한 방법으로 순회
  • 책임 분리: 저장과 순회를 별도 객체가 담당

5. 예시 코드

Step 1: 인터페이스 정의

// Iterator 인터페이스
public interface Iterator {
    boolean hasNext();
    Object next();
}

// Menu(Aggregate) 인터페이스
public interface Menu {
    Iterator createIterator();
}

Step 2: DinerMenuIterator 구현 (배열용)

public class DinerMenuIterator implements Iterator {
    MenuItem[] items;
    int position = 0;  // 현재 위치
    
    public DinerMenuIterator(MenuItem[] items) {
        this.items = items;
    }
    
    // 다음 요소 반환
    public Object next() {
        MenuItem menuItem = items[position];
        position++;
        return menuItem;
    }
    
    // 다음 요소가 있는지 확인
    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        }
        return true;
    }
}

Step 3: Menu 구현

// 배열 기반 메뉴
public class DinerMenu implements Menu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;
    
    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem("Vegetarian BLT", "Bacon with Lettuce & tomato", true, 2.99);
        addItem("BLT", "Bacon with Lettuce & tomato", false, 2.99);
    }
    
    public void addItem(String name, String description, 
                        boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.err.println("Sorry, menu is full!");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems++;
        }
    }
    
    // Iterator 생성 (핵심!)
    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }
}

// ArrayList 기반 메뉴
public class PancakeHouseMenu implements Menu {
    ArrayList<MenuItem> menuItems;
    
    public PancakeHouseMenu() {
        menuItems = new ArrayList<MenuItem>();
        addItem("Pancake Breakfast", "Pancakes with eggs", false, 2.99);
        addItem("Blueberry Pancakes", "Pancakes with blueberries", true, 3.49);
    }
    
    public void addItem(String name, String description, 
                        boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }
    
    // ArrayList는 이미 iterator() 제공
    public Iterator createIterator() {
        return new PancakeHouseMenuIterator(menuItems);
    }
}

Step 4: 클라이언트 (Waitress) 구현

public class Waitress {
    Menu pancakeHouseMenu;
    Menu dinerMenu;
    
    // 생성자에서 메뉴들을 받음
    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }
    
    // 모든 메뉴 출력
    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();
        
        System.out.println("MENU\n----\nBREAKFAST");
        printMenu(pancakeIterator);
        
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);
    }
    
    // 통일된 방식으로 순회 (핵심!)
    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = (MenuItem) iterator.next();
            System.out.print(menuItem.getName() + ", ");
            System.out.print(menuItem.getPrice() + " -- ");
            System.out.println(menuItem.getDescription());
        }
    }
}

Step 5: 실행

public class MenuTestDrive {
    public static void main(String[] args) {
        Menu pancakeHouseMenu = new PancakeHouseMenu();
        Menu dinerMenu = new DinerMenu();
        
        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
        
        // 내부 구조를 몰라도 통일된 방식으로 출력!
        waitress.printMenu();
    }
}

출력 결과

MENU
----
BREAKFAST
Pancake Breakfast, 2.99 -- Pancakes with eggs
Blueberry Pancakes, 3.49 -- Pancakes with blueberries

LUNCH
Vegetarian BLT, 2.99 -- Bacon with Lettuce & tomato
BLT, 2.99 -- Bacon with Lettuce & tomato

6. Java 내장 Iterator 사용

Java의 java.util.Iterator

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();  // 선택적 기능
}

Java Iterator 활용

import java.util.Iterator;

public class PancakeHouseMenu implements Menu {
    ArrayList<MenuItem> menuItems;
    
    public Iterator createIterator() {
        // ArrayList가 제공하는 iterator 사용
        return menuItems.iterator();
    }
}

public class CafeMenu implements Menu {
    Hashtable<String, MenuItem> menuItems;
    
    public Iterator createIterator() {
        // Hashtable의 values를 iterator로 반환
        return menuItems.values().iterator();
    }
}

Java 5+ for-each 문

// Iterator를 사용하지 않고도 순회 가능
ArrayList<MenuItem> items = new ArrayList<>();
for (MenuItem item : items) {
    System.out.println(item.getName());
}

7. 핵심 정리

Iterator Pattern의 구성

요소역할왜 Interface?
Iterator컬렉션 순회 방법 제공다양한 순회 방식 지원 가능
Aggregate(Menu)Iterator 생성 책임다양한 컬렉션 구조 지원 가능
ConcreteIterator실제 순회 구현각 자료구조에 맞는 순회 방식
ConcreteAggregate실제 데이터 저장내부 구조 캡슐화

언제 사용하는가?

  • 컬렉션의 내부 구조를 노출하지 않고 순회하고 싶을 때
  • 다양한 자료구조를 통일된 방법으로 순회하고 싶을 때
  • 여러 개의 동시 순회가 필요할 때
  • 순회 책임을 컬렉션에서 분리하고 싶을 때

핵심 원칙

  • 단일 책임 원칙 (Single Responsibility): 저장과 순회를 분리
  • 캡슐화: 내부 자료구조를 숨김
  • 다형성: 인터페이스를 통해 다양한 컬렉션 처리
  • 느슨한 결합: 클라이언트는 인터페이스만 의존

장점

  • 컬렉션 내부 구조 변경 시 클라이언트 코드 수정 불필요
  • 동일한 컬렉션을 여러 방식으로 순회 가능
  • 순회 로직이 간결하고 명확해짐

단점

  • 간단한 컬렉션에는 오버엔지니어링일 수 있음
  • Iterator 객체 생성 비용
profile
다들 응원합니다.

0개의 댓글