반복자 패턴이란?
여러 객체가 모여있는 컬렉션의 구현 방법을 노출시키지 않고 동일한 반복을 하게 한다!
Why?
어떤 문제가 있길래?
- 컬렉션이든 배열이든 값에 접근하는 방식이 다르다.
아래와 같이 저녁메뉴와 아침메뉴에 대한 클래스가 있고 각각 다른 형식으로 객체들의 집합을 정의했습니다.
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("저녁 A 세트", "저녁 세트 구성", false, 9.99);
addItem("저녁 B 세트", "저녁 채식주의 구성", true, 8.99);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
if (numberOfItems >= MAX_ITEMS) {
System.out.println("추가 불가");
}
menuItems[numberOfItems] = new MenuItem(name, description, vegetarian, price);
numberOfItems++;
}
public MenuItem[] getMenuItems() {
return menuItems;
}
}
public class PancakeHouseMenu {
List<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<>();
addItem("A 세트", "A 구성", true, 2.99);
addItem("B 세트", "B 구성", false, 7.99);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
menuItems.add(new MenuItem(name, description, vegetarian, price));
}
public List<MenuItem> getMenuItems() {
return menuItems;
}
}
이러면 무슨 일이 발생할까요?
메뉴 구성을 확인하기 위해 MenuItem의 집합을 가져오게 되면 서로 다른 접근 방식으로 2개의 순환문이 필요하고 불필요한 코드가 중복되게 됩니다.
어떻게 바꿀까?
What?
공통된 반복을 하기 위해 추상화 하자!
이를 위해 Iterator인터페이스를 정의하여 공통된 기능으로 동작되도록 해보겠습니다.
public interface Iterator {
boolean hasNext(); // 다음이 있는가?
MenuItem next(); // 값을 가져와라!
}
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
}
return true;
}
@Override
public MenuItem next() {
MenuItem menuItem = items[position];
position += 1;
return menuItem;
}
}
public class PancakeHouseMenuIterator implements Iterator{
List<MenuItem> items;
int position = 0;
public PancakeHouseMenuIterator(List<MenuItem> items) {
this.items = items;
}
@Override
public boolean hasNext() {
if (position >= items.size() || items.get(position) == null) {
return false;
}
return true;
}
@Override
public MenuItem next() {
MenuItem menuItem = items.get(position);
position += 1;
return menuItem;
}
}
이렇게 공통 인터페이스를 통해 구현을 강제하고 반복자를 대신 해주게 되면, 서로 다른 구현이더라도 공통된 반복을 할 수 있게 된다.
// 이러한 코드에서
public void print(List<MenuItem> breakfastItems, MenuItem[] menuItems) {
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem item = breakfastItems.get(i);
System.out.println(item.getName());
System.out.println(item.getPrice());
System.out.println(item.getDescription());
}
for (int i = 0; i < menuItems.length; i++) {
MenuItem item = menuItems[i];
System.out.println(item.getName());
System.out.println(item.getPrice());
System.out.println(item.getDescription());
}
}
// 반복자만 알면 되므로
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.println(menuItem.getName() + "_" + menuItem.getDescription());
}
}
와 같이 변경된 것을 볼 수 있다.
근데 생각할 수 있겠죠, 자바 개발자가 이걸 과연 구현 안했을까?
물론 했습니다.
java.util.Iterator를 사용해서 구현할 수 있습니다.
How?
// 배열의 경우, Collection으로 변경해줘야함
public Iterator<MenuItem> createIterator() {
return Arrays.asList(menuItems).iterator();
}
// 리스트의 경우
public Iterator createIterator() {
return menuItems.iterator();
}
장단점
장점
- 단일 책임 원치을 지키고 있다. 왜? 하나의 클래스에서 반복에 대한 책임을 가지고 있다.
- 개방 / 폐쇄 원칙을 지키고 있다.
단점
- 단순할 경우 굳이?
- 직접 탐색하는 것보다 효율이 안좋을 수 있다. (HashSet, HashMap)
사용하는 곳
- 순서가 없는 컬렉션? : 사용 가능하다. 하지만 정렬이 안되어있는 것은 똑같다.
- 특정 기능을 통한 반복을 하고 싶을 때 : 직접 구현
'디자인패턴' 카테고리의 다른 글
[디자인 패턴 - 번외] 플라이웨이트 (0) | 2023.07.28 |
---|---|
[디자인 패턴] - 컴포지트 패턴 (0) | 2023.06.30 |
[디자인 패턴] 템플릿 메소드 패턴 (0) | 2023.06.28 |
[디자인 패턴] - 전략 패턴 (0) | 2023.06.16 |