이터레이터 패턴
컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공해 준다.
- Clinet는 추상화된 Aggregate와 Interator만 참조하여 원하는 기능을 수행할 수 있다.
예제
- 가게에서 점심 메뉴는 List로 구현하고 저녁 메뉴는 HashMap으로 구현되어 있다고 하자.
- 두 메뉴를 한 번에 나타내고 싶다고 하였을 때 이터레이터 패턴을 사용할 수 있다.
Aggregate
public interface Menu {
public Iterator createIterator();
}
- Iterator를 생성하는 인터페이스가 된다. Iterator는 자바에서 제공하는 API이다.
ConcreteAggregate
public class MenuItem {
private String name;
private String description;
private double price;
public MenuItem(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
}
- 우선 각 메뉴들을 표현하기 위한 MenuItem을 정의한다.
LunchMenu
public class LunchMenu implements Menu {
private List<MenuItem> menuItems;
public LunchMenu() {
menuItems = new ArrayList<>();
addItem("토스트 세트"
, "기본 토스트에 아메리카노 포함"
, 4000);
addItem("아보카도 토스트 세트"
, "아보카도와 다양항 야채들이 들어간 샌드위치에 아메리카노 포함"
, 5000);
}
private void addItem(String name, String description, int price) {
MenuItem menuItem = new MenuItem(name, description, price);
menuItems.add(menuItem);
}
@Override
public Iterator createIterator() {
return menuItems.iterator();
}
}
- LunchMenu는 리스트로 구현하였다.
- 오버라이드 한 createIterator에서 자바가 제공해주는 API인 itertor()로 반환하였다.
DinnerMenu
public class DinerMenu implements Menu {
private Map<String, MenuItem> menuItems;
public DinerMenu() {
menuItems = new HashMap<>();
addItem("알리오 올리오 파스타"
, "베이컨, 마늘, 핫페퍼가 들어간 파스타"
, 10000);
addItem("마르게리타 피자"
, "토마토, 모차렐라, 바질이 들어간 피자"
, 12000);
}
private void addItem(String name, String description, int price) {
MenuItem menuItem = new MenuItem(name, description, price);
menuItems.put(menuItem.getName(), menuItem);
}
@Override
public Iterator createIterator() {
return menuItems.values().iterator();
}
}
- HashMap으로 구성된 DinnerMenu이다.
- 이 또한 자바에서 제공해주는 API인 iterator로 전환하였다.
ConcreteAggregate에서는 ConcreteIterator를 참조한다. 하지만 여기서는 그냥 JAVA에서 제공해주는 Iterator를 사용하여 생략하였다.
Client
public class Waitress {
private Menu lunchMenu;
private Menu dinerMenu;
public Waitress(Menu lunchMenu, Menu dinerMenu) {
this.lunchMenu = lunchMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator lunchIterator = lunchMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
System.out.println("===== 점심 식사 메뉴 =====");
printMenu(lunchIterator);
System.out.println("===== 저녁 식사 메뉴 =====");
printMenu(dinerIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getDescription() + ", ");
System.out.println(menuItem.getPrice());
}
}
}
- Waitress가 Client이다.
- 웨이터는 추상화된 Menu와 Iterator만으로 메뉴들 출력할 수 있다.
- Menu들의 세부사항들이 어떤 컬렉션으로 구현되었는지 알 필요 없다.
- 모든 컬렉션 타입은 Iterator로 변환되기 때문이다.
- 웨이터는 모든 메뉴들을 Iterator를 통해 접근하고 사용만 하면 된다.
- 즉 책임이 분리된 설계를 할 수 있게 되었다.
Test
public class Main {
public static void main(String[] args) {
LunchMenu lunchMenu = new LunchMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(lunchMenu, dinerMenu);
waitress.printMenu();
}
}
===== 점심 식사 메뉴 =====
토스트 세트, 기본 토스트에 아메리카노 포함, 4000.0
아보카도 토스트 세트, 아보카도와 다양항 야채들이 들어간 샌드위치에 아메리카노 포함, 5000.0
===== 저녁 식사 메뉴 =====
마르게리타 피자, 토마토, 모차렐라, 바질이 들어간 피자, 12000.0
알리오 올리오 파스타, 베이컨, 마늘, 핫페퍼가 들어간 파스타, 10000.0
- 테스트를 통해 위의 설계가 정상적으로 동작하는 것을 확인할 수 있다.
단일 역할 원칙
- 클래스를 바꾸는 이유는 한 가지뿐이어야 한다.
위의 예에서 이터레이터를 구현하지 않고 그 기능들을 LunchMenu, DinerMenu에 넣었다면 어떻게 될까?
- 그렇게 된다면 컬렉션의 값을 바꿀 때도 클래스가 변경된다.
- 반복자 관련 기능(이터레이터가 해준 기능)을 바꿀 때도 클래스가 변경되어야 한다.
- 변경되는 이유가 두 가지가 되므로 단일 역할 원칙을 지키지 못하게 된다.
- 이러한 설계는 응집도가 낮아져 유연한 설계가 불가능하게 된다.
'디자인 패턴' 카테고리의 다른 글
스테이트 패턴(State Pattern) (0) | 2019.12.20 |
---|---|
컴포지트 패턴(Composite Pattern) (0) | 2019.12.20 |
템플릿 메서드 패턴(Template Method Pattern) (0) | 2019.12.17 |
퍼사드 패턴(Facade Pattern), 최소 지식 원칙 (0) | 2019.12.16 |
어댑터 패턴(Adapter Pattern) (0) | 2019.12.16 |