행위패턴 > 이터레이터 *
객체를 컬렉션에 저장할 때는 배열, 스택, 해시테이블 등 장단점을 따져 적합한 컬렉션을 선택해 이용한다.
클라이언트에서 모든 객체들에 일일이 접근하는 작업을 하고자할 때, 어떤 식으로 저장했는지 노출하지 않으면서 접근할 수 있도록 하는 방법과 객체들로 구성된 슈퍼 컬렉션에 대해 알아본다.
A 식당과 B 식당이 합병하기로 했는데 A 식당은 메뉴를 ArrayList 에 B식당은 배열에 저장한다.
Waitress 는 메뉴를 출력하려면 배열과 리스트 각각의 순환문을 작성해야 한다.
Waitress 가 구상클래스 MenuItem[] 과 ArrayList 에 직접 연결되어있다.
public class Waitress {
PancakeHouseMenu pancake;
DinerMenu diner;
public Waitress(PancakeHouseMenu pancake,DinerMenu diner){
this.pancake=pancake;
this.diner=diner;
}
public void printMenu() {
List<MenuItem> list = pancake.getMenuItems();
for(MenuItem m : list){
System.out.println(m.name);
System.out.println(m.desc);
System.out.println(m.price);
}
MenuItem[] menu = diner.getMenuItems();
for(int i=0;i<menu.length;i++){
System.out.println(menu[i].name);
System.out.println(menu[i].desc);
System.out.println(menu[i].price);
}
}
}
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numOfItems = 0;
MenuItem [] menu;
public DinerMenu(){
menu = new MenuItem[MAX_ITEMS];
addItem("채식 BLT", "콩고기", 2000, true);
addItem("BLT", "돼지고기", 2000, false);
addItem("햄버거", "새우", 5000, false);
addItem("피자", "페퍼로니", 3000, false);
addItem("파스타", "알리오올리오", 5000, true);
}
public void addItem(String name,String desc,double price,boolean vegeterian){
if(numOfItems>=MAX_ITEMS){
System.out.println("no more");
} else {
MenuItem m = new MenuItem(name, desc, price, vegeterian);
menu[numOfItems++] = m;
}
}
//배열로 된 메뉴 반환
public MenuItem [] getMenuItems(){
return menu;
}
}
다른 컬렉션을 하나의 반복문으로 처리하기 위해 Iterator 인터페이스를 만든다.
// Iterator 직접 만들지 않고 java.util 에 있는 iterator 를 이용해도 무관하다
package iterator;
public interface Iterator {
public boolean hasNext();
public Object next();
}
// Concrete Iterator
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 Object next() {
return (MenuItem)items[position++];
}
}
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numOfItems = 0;
MenuItem [] menu;
public DinerMenu(){ .. }
public void addItem(String name,String desc,double price,boolean vegeterian){ .. }
// 이터레이터 반환으로 수정
public Iterator getMenuItems(){
return new DinerMenuIterator(menu);
}
}
* Watiress 는 더이상 Diner 와 Pancake 이 어떤 컬렉션으로 메뉴를 저장하고 있는지 알 필요가 없어졌다.
* Watiress 는 오직 Iterator 만 알고 있으며, 컬렉션 내부에 접근하기 위한 반복 작업의 캡슐화 되어있다.
public class Waitress {
PancakeHouseMenu pancakeHouseMenu;
DinerMenu dinerMenu;
public Waitress(PancakeHouseMenu pancakeHouseMenu,DinerMenu dinerMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu(){
Iterator pancakeIT = pancakeHouseMenu.getMenuItems();
Iterator dinerMenuIT = dinerMenu.getMenuItems();
printMenu(pancakeIT);
printMenu(dinerMenuIT);
}
//리스트, 배열 다른 컬렉션 형태를 반복문을 하나로 통합!
public void printMenu(Iterator it){
while(it.hasNext()){
MenuItem m = (MenuItem) it.next();
System.out.println(m.name);
System.out.println(m.desc);
System.out.println(m.price);
}
}
}
하지만 아직 두 개의 구상 메뉴 클래스에 의존하고 있다는 문제는 갖고 있다.
두 메뉴 클래스에 getMenuItems 는 각각 Panacke 와 Diner 의 Iterator를 반환하는 공통 메서드이다.
공통 메서드를 가진 Menu 인터페이스를 만들고 Panacke 와 Diner 에서 이를 상속한다.
// Aggregate
public interface Menu {
public Iterator getMenuItems();
}
// Concrete Aggregate
public class DinerMenu implements Menu {
static final int MAX_ITEMS = 6;
int numOfItems = 0;
MenuItem [] menu;
public DinerMenu(){ .. }
public void addItem(String name,String desc,double price,boolean vegeterian){ .. }
public Iterator getMenuItems(){
return new DinerMenuIterator(menu);
}
}
* 그럼 Waitress 에서 두 개의 구상클래스 대신 한 개의 인터페이스에만 의존할 수 있게 된다.
특정 구현이 아닌 인터페이스에 맞춰 프로그래밍하는 원칙을 지키게 되고 의존성이 줄일 수 있다.
// Client
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu,Menu dinerMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu(){ .. }
public void printMenu(Iterator it){ .. }
}
* 반복자를 이용하면 다형적인 코드를 만들 수 있다는 것의 의미
Iterator 를 매개변수로 받는 메서드를 만들면 다형적인 반복작업을 사용한다고 할 수 있다.
Iterator 를 지원하기만 하면 어떤 컬렉션에도 사용 가능한 코드가 만들어지기 때문이다. 컬렉션의 구체적인 구현 방식에는 신경쓰지 않아도 된다.
* 집합체 - Diner,Pancake 내에서 내부 컬렉션과 관련된 반복자 메서드를 구현하는 것
클래스의 역할은 집합체를 관리하는 데 있다.
집합체 관리 와 반복자 메서드는 두 개의 다른 역할이다.
클래스가 수정되는 것은 다른 영향도를 만들 수 있기 때문에 수정을 가능한 줄이는 것이 좋다.
클래스가 컬렉션 뿐만 아니라 반복자 관련 기능도 갖고 있을 때, 클래스는 두 가지 이유로 수정될 수 있다.
만약 새로운 요구사항에 의해 집합체의 컬렉션의 종류가 달라지게 되는 경우, 반복자 관련 기능이 변경되었을 경우.
이는 클래스를 바꾸는 이유는 한가지 뿐이여야 한다는 디자인 원칙에 위반된다.
출처:
Head First Design Patterns