-
행위패턴 > 이터레이터 *기타/디자인패턴 2021. 2. 11. 15:19
객체를 컬렉션에 저장할 때는 배열, 스택, 해시테이블 등 장단점을 따져 적합한 컬렉션을 선택해 이용한다.
클라이언트에서 모든 객체들에 일일이 접근하는 작업을 하고자할 때, 어떤 식으로 저장했는지 노출하지 않으면서 접근할 수 있도록 하는 방법과 객체들로 구성된 슈퍼 컬렉션에 대해 알아본다.
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
반응형'기타 > 디자인패턴' 카테고리의 다른 글
행위패턴 > 상태(State) * (0) 2021.02.12 행위패턴 > 옵저버 * (0) 2021.02.12 행위패턴 > 커맨드 * (0) 2021.02.11 구조패턴 > 퍼싸드 * (0) 2021.02.11 구조패턴 > 데코레이터 * (0) 2021.02.11