본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

Iterator

by 민휘 2023. 2. 24.

핵심 의도

컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공한다. 집합체를 사용하는 클라이언트는 집합체의 저장 방식을 몰라도 반복 작업을 할 수 있고, 집합체 역시 구체적인 저장방식을 공개하지 않아도 되므로 더 유연하고 다형적인 코드를 작성할 수 있다.

 

적용 상황

서로 다른 타입의 컬렉션에 대해 동일한 인터페이스로 모든 항목에 접근하고 싶을 때 사용한다. 예를 들어 배열이나 ArrayList를 가진 객체를 인터페이스로 한번 감싸서 동일한 인터페이스로 모든 항목에 접근할 수 있다.

자바에서 제공하는 Iterator 인터페이스를 사용하면 편리하다. 클라이언트 사용 객체가 자신이 가진 컬렉션에 해당하는 Iterator 구현체를 반환하는 메소드를 두면 된다. 배열은 Iterator 구현체가 따로 없으므로 만들어서 반환한다.

자바의 모든 컬렉션 유형은 Iterable 인터페이스를 구현한다. Iterable은 Iterator 인터페이스를 구현하는, 반복자를 리턴하는 iterator() 메소드가 들어있다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

클라이언트가 사용하려고 하는 컬렉션과 그 부가기능을 제공하는 책임은 ConcreteAggregate에게 있으며, 여러 종류의 객체가 존재할 수 있으므로 Aggregate 인터페이스로 접근한다. ConcreteAggregate가 가진 컬렉션의 모든 항목을 접근할 수 있게 하는 기능은 ConcreteIterator에게 있다. ConcreteAggregate가 다른 컬렉션을 가지고 있을 수 있으므로 Iterator도 인터페이스로 접근한다.

 

구현 포인트

자바에서 제공하는 Iterator 인터페이스는 hasNext(), next(), remove()를 구현하도록 하고 있다. 만약 remove() 구현이 필요하지 않다면 예외를 던져 처리한다.

반복자 패턴을 사용하면 집합체에서 내부 컬렉션 관리 기능과 반복자용 메소드 기능을 분리할 수 있다. 클래스의 변경 이유를 분리한 것이므로 SRP(단일 책임 원칙)를 지키고 있다.

 

적용 예시

요구사항

배열로 메뉴를 관리하는 Diner 식당과 리스트를 사용하는 PancakeHouse가 합병한다. 종업원은 동일한 인터페이스로 두 식당의 메뉴 목록을 설명할 수 있어야 한다.

 

설계

종업원은 Menu라는 인터페이스로 두 식당의 메뉴 객체에 접근할 수 있다. Menu 구현체들은 반복자 관련 구현체인 Iterator 객체를 반환한다. 각각 배열과 리스트에 맞는 Iterator을 반환하는데, 배열은 java util에서 제공하는 반복자가 없으므로 따로 생성해 반환한다.

 

코드

Menu

import java.util.Iterator;

public interface Menu {
	public Iterator<?> createIterator();
}

PancakeHouseMenu

import java.util.ArrayList;
import java.util.Iterator;

public class PancakeHouseMenu implements Menu {
	ArrayList<MenuItem> menuItems;
 
	public PancakeHouseMenu() {
		menuItems = new ArrayList<MenuItem>();
    
		addItem("K&B's Pancake Breakfast", 
			"Pancakes with scrambled eggs and toast", 
			true,
			2.99);
 
		addItem("Regular Pancake Breakfast", 
			"Pancakes with fried eggs, sausage", 
			false,
			2.99);
 
		addItem("Blueberry Pancakes",
			"Pancakes made with fresh blueberries and blueberry syrup",
			true,
			3.49);
 
		addItem("Waffles",
			"Waffles with your choice of blueberries or strawberries",
			true,
			3.59);
	}

	public void addItem(String name, String description,
	                    boolean vegetarian, double price)
	{
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menuItem);
	}
 
	public ArrayList<MenuItem> getMenuItems() {
		return menuItems;
	}
  
	public Iterator<MenuItem> createIterator() {
		return menuItems.iterator();
	}
  
	// other menu methods here
}

DinerMenu

import java.util.Iterator;

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",
			"(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
		addItem("BLT",
			"Bacon with lettuce & tomato on whole wheat", false, 2.99);
		addItem("Soup of the day",
			"Soup of the day, with a side of potato salad", false, 3.29);
		addItem("Hotdog",
			"A hot dog, with sauerkraut, relish, onions, topped with cheese",
			false, 3.05);
		addItem("Steamed Veggies and Brown Rice",
			"Steamed vegetables over brown rice", true, 3.99);
		addItem("Pasta",
			"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
			true, 3.89);
	}
  
	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!  Can't add item to menu");
		} else {
			menuItems[numberOfItems] = menuItem;
			numberOfItems = numberOfItems + 1;
		}
	}
 
	public MenuItem[] getMenuItems() {
		return menuItems;
	}
  
	public Iterator<MenuItem> createIterator() {
		return new DinerMenuIterator(menuItems);
		//return new AlternatingDinerMenuIterator(menuItems);
	}
 
	// other menu methods here
}

DinerMenuIterator

import java.util.Iterator;
  
public class DinerMenuIterator implements Iterator<MenuItem> {
	MenuItem[] list;
	int position = 0;
 
	public DinerMenuIterator(MenuItem[] list) {
		this.list = list;
	}
 
	public MenuItem next() {
		MenuItem menuItem = list[position];
		position = position + 1;
		return menuItem;
	}
 
	public boolean hasNext() {
		if (position >= list.length || list[position] == null) {
			return false;
		} else {
			return true;
		}
	}
 
	public void remove() {
		if (position <= 0) {
			throw new IllegalStateException
				("You can't remove an item until you've done at least one next()");
		}
		if (list[position-1] != null) {
			for (int i = position-1; i < (list.length-1); i++) {
				list[i] = list[i+1];
			}
			list[list.length-1] = null;
		}
	}

}

Waitress

import java.util.*;
  
     
public class Waitress {
	ArrayList<Menu> menus;
     
  
	public Waitress(ArrayList<Menu> menus) {
		this.menus = menus;
	}
   
	public void printMenu() {
		Iterator<?> menuIterator = menus.iterator();
		while(menuIterator.hasNext()) {
			Menu menu = (Menu)menuIterator.next();
			printMenu(menu.createIterator());
		}
	}
   
	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());
		}
	}
}

MainTestDrive

public class MenuTestDrive {
	public static void main(String args[]) {
		PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
		DinerMenu dinerMenu = new DinerMenu();
		ArrayList<Menu> menus = new ArrayList<Menu>();
		menus.add(pancakeHouseMenu);
		menus.add(dinerMenu);
		Waitress waitress = new Waitress(menus);
		waitress.printMenu();
	}
}

 

 

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

State  (0) 2023.02.24
Composite  (0) 2023.02.24
Template Method  (0) 2023.02.24
Facade  (0) 2023.02.24
Adapter  (0) 2023.02.24