반복자 패턴
반복자 패턴(Iterator Pattern)은 컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공합니다.
즉, 반복자 패턴을 통해 접근기능과 컬렉션 자료구조를 분리시켜서 객체화합니다.
그리고 반복자 패턴을 통해 서로 다른 구조를 가지고 있는 저장 객체에 대해서 접근하기 위해 접근 기능을 반복자(Iterator) interface로 통일 시킬 수 있습니다.
Iterator
다른 구조를 가지고 있는 저장 객체에 대해서 접근하기 위해 통일할 인터페이스 (자바에서 실제로 Iterator 인터페이스를 제공한다.)
ConcreteIterator(Iterator1)
반복 작업 중에 현재 위치를 관리하는 일을 맡는다. 실질적인 반복 작업을 구현한다.
ConcreteAggregate(Aggregate1)
실질적인 객체 컬렉션을 가지고 있으며, 그 안에 들어있는 컬렉션을 Iterator 로 반환하는 메소드를 구현한다.
Aggregate
공통된 인터페이스가 있으면 클라이언트는 매우 편리하게 작업을 처리할 수 있다. 다양한 객체 컬렉션을 가지고 있는 저장 객체들의 동일 인터페이스이다. 이 객체들은 모두 creatIterator() 메소드를 가지므로 똑같은 인터페이스를 상속할 수 있다.
위의 UML 클래스 다이어그램에서, Client 클래스는 (1) Iterator 객체를 생성하기 위해 Aggregate 인터페이스를 참조하고(createIterator()) (2) Aggregate 객체를 탐색하기 위해 Iterator 인터페이스를 참조합니다 (next(), hasNext()). Iterator1 클래스는 Iterator 인터페이스를 구현하며 Aggregate1 클래스에 접근합니다.
UML Sequence Diagram은 실행 시 상호작용을 보여줍니다. Client 객체는 Aggregate1 객체에서 createIterator()를 호출하고, 이로써 Iterator1 객체가 생성되어 Client에 반환됩니다. 그 후 Client는 Iterator1을 사용하여 Aggregate1 객체의 요소들을 순회합니다.
예제 코드입니다.
ConcreteIterator: DinnerMenuIterator
import java.util.Iterator;
public class DinnerMenuIterator implements Iterator<MenuItem> {
MenuItem[] items;
int position = 0;
public DinnerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
return false;
}
@Override
public MenuItem next() {
return null;
}
}
자바에서 제공하는 Iterator를 사용하였으며,
실질적인 컬렉션(자료구조)을 가지고있는 ConcreteAggregate로부터 컬렉션을 받아와서 각 항목에 접근할 수 있게 해주는 기능을 책임지는 클래스입니다.
Aggregate: Menu
import java.util.Iterator;
public interface Menu {
public Iterator<MenuItem> createIterator();
}
ConcreteAggregate DinnerMenu
import java.util.Iterator;
public class DinnerMenu implements Menu
{
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinnerMenu() {
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;
}
@Override
public Iterator<MenuItem> createIterator() {
return new DinnerMenuIterator(menuItems);
}
}
가지고 있는 컬렉션에 대하여 Iterator를 생성합니다.
만약 컬렉션이 ArrayList, LinkedList 등의 자바 자료구조라면 내부에 iterator 메소드를 가지고있어서 ConcreteIterator조차 만들 필요가 사라집니다.
Client Waitress
import java.util.Iterator;
public class Waitress {
Menu dinnerMenu;
public Waitress(Menu dinnerMenu) {
this.dinnerMenu = dinnerMenu;
}
public void printMenu() {
printMenu(dinnerMenu.createIterator());
}
public void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem) iterator.next();
System.out.println(menuItem.getName());
}
}
}
이제 다른 구조를 가지고 있는 저장 객체가 와도 반복자 패턴을 사용하여 printMenu(Iterator iterator) 메소드를 활용할 수 있다.
디자인 원칙: 단일 책임 원칙
하나의 클래스는 하나의 역할만 맡아야한다.
단일 책임 원칙은 클래스나 모듈이 단 하나의 변경 이유만을 가져야 한다는 원칙으로, 이를 통해 코드의 응집성을 높이고 유연성을 강화할 수 있습니다.
응집도
클래스 또는 모듈이 특정 목적이나 역할을 얼마나 일관되게 지원하는지를 나타내는 척도
어떤 모듈이나 클래스의 응집도가 높다는 것은 서로 연관된 기능이 묶여있다는 것을 뜻한다.
즉, 클래스가 2개 이상의 역할을 맡고 있는 클래스에 비해 하나만 맡고 있는 클래스가 응집도가 높다고 할 수 있다.
반복자 패턴은 단일 책임 원칙을 지킵니다.
컬렉션과 이터레이터 분리
- 반복자 패턴에서 컬렉션은 요소를 가지고 있고, 이터레이터는 이 요소를 순회하고 접근하는 역할을 합니다. 즉, 컬렉션은 자신의 요소에 대한 저장 및 관리 책임만을 갖고, Iterator는 순회 및 접근에 대한 책임만을 갖습니다.
단일 책임을 갖는 인터페이스 및 클래스
- Iterator 인터페이스는 순회와 접근에 관련된 메서드를 정의하며, 구현 클래스들은 이러한 메서드를 구체적으로 구현합니다. Iterator는 오직 순회 및 접근에만 집중하며 다른 책임은 가지지 않습니다.
이로써 한 가지 책임에 집중하여 객체 간의 결합을 줄이고, 코드의 유지보수성과 재사용성을 향상시킵니다.
참고 자료
https://github.com/IT-Book-Organization/HeadFirst-DesignPattern/blob/main/Chapter_09/README.md