일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- DB
- javabasic
- 직장인인강
- 디자인
- 리눅스
- linux
- 패스트캠퍼스
- String
- 웹
- 디자인패턴
- 자바연습문제
- 패캠챌린지
- 한번에끝내는JavaSpring웹개발마스터초격차패키지Online강의
- DesignPattern
- 자바
- js
- 자바기초
- 국비
- 자바기본
- 패스트캠퍼스후기
- ncs
- 데이터베이스
- java기초
- 직장인자기계발
- 재택근무
- 스프링
- java
- 자바예제
- Spring
- 한번에끝내는JavaSpring웹개발마스터초격차패키지Online
- Today
- Total
FIF's 코딩팩토리
이터레이터 패턴(Iterator Pattern) 정리 본문
이터레이터 패턴(Iterator Pattern) 정의컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공한다. |
컬렉션 안에 들어있는 모든 항목에 접근하는 방식이 통일되어 있으면 어떤 종류의 집합체에 관해서도 사용할 수 있는 다형적인 코드를 만들 수 있다.
이터레이터 패턴을 사용하면 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아닌 반복자 객체에서 맡게 된다.
이렇게 하면 집합체의 인터페이스 및 구현이 간단해질 뿐 아니라, 집합체에서는 반복작업에서 손을 떼고 원래 자신이 할일(객체 컬렉션 관리)에만 전념할 수 있다.
이터레이터 패턴 클래스 다이어그램
두개의 서로다른 식당이 있고, 각각의 식당에서 메뉴를 구현한다고 가정한다.
MenuItem.java
public class MenuItem {
String name;
String description;
boolean vegetrain;
double price;
public MenuItem(String name, String description, boolean vegetrain, double price) {
this.name = name;
this.description = description;
this.vegetrain = vegetrain;
this.price = price;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPriace() {
return price;
}
public String isvegetrain() {
return vegetrain;
}
}
PancakeHouseMenu.java
import java.util.ArrayList;
public class PancakeHouseMenu {
ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
this.menuItems = new ArrayList();
addItem("K&B 팬케이크 세트", "스크램블 애그와 토스트가 곁들여진 팬케이크",true,2.99);
addItem("레귤러 팬케이크 세트" , "달걀 후라이와 소시지가 곁들여진 팬케이크", false, 2.99);
addItem("블루베리 팬케이크", "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크", true, 3.49);
addItem("와플","와플, 취향에 따라 블루베리나 딸기를 얹을 수 있습니다." true, 3.59);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItem.add(menuItem);
}
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}
// 기타 메소드들...
}
DinerMenu.java
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinerMenu() {
this.menuItems = new MenuItem[MAX_ITEMS];
addItem("채식주의자용 BLT", "통밀 위에 (식물성)베이컨,상추,토마토를 얹은 메뉴", true, 2.99);
addItem("BLT", "통밀 위에 베이컨, 상추,토마토를 얹은 메뉴", false, 2.99);
addItem("오늘의 스프", "감자 샐러드를 곁들인 오늘의 스프", false, 3.29);
addItem("핫도그", "샤워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그", false, 3.05);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.out.println("죄송합니다, 메뉴가 꽉찾습니다. 추가주문할 수 없습니다.");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems + 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
// 기타 메소드들...
}
위와 같이 두가지 서로 다른 메뉴 표현 방식이 있을 때, 어떤 문제가 생길 수 있을까?
두 메뉴를 사용하는 클라이언트를 만들어보자.
클라이언트 기능은 5가지로 정한다.
1. printMenu() – 메뉴에 있는 모든 항목을 출력
2. printBreakFastMenu() – 아침 식사 항목만 출력
3. printLunchMenu() – 점심 식사 항목만 출력
4. printVegetarianMenu() – 채식주의자용 메뉴 항목만 출력
5. isItemVegetarian(name) – name 항목이 채식주의자용이면 true, 아니면 false 리턴
각 메뉴에 들어있는 모든항목을 출력하려면
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();
DinerMenu dinerMenu = new DinerMenu();
MenuItem[] lunchItems = dinerMenu.getMenuItems();
for ( int i=0; i < breakfaseItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
System.out.println(menuItem.getName());
System.out.println(menuItem.getPrice());
System.out.println(menuItem.getDescription());
}
for ( int i=0; i < lunchItems.length; i++) {
MenuItem menuItem = lunchItems[i];
System.out.println(menuItem.getName());
System.out.println(menuItem.getPrice());
System.out.println(menuItem.getDescription());
}
다른 모든 메소드들도 결국 위에 있는 코드랑 비슷한 식으로 작성해야 한다.
항상 두 메뉴를 이용하고, 각 아이템에 대해서 반복적인 작업을 수행하기 위해 두 개의 순환문을 써야 한다.
이후에 식당이 더 추가된다면 이런 상황이 계속 반복된다.
디자인 원칙중 “바뀌는 부분을 캡슐화 하라”는 내용이 있었다.
반복을 캡슐화 할 수 있을까?
아래와 같이 반복작업을 캡슐화한 Iterator 객체를 만들면 된다.
Iterator <MenuItem> iterator = breakfastMenu.createIterator();
while(iterator.hasNext(){
MenuItem menuItem = iterator.next();
}
Iterator<MenuItem> iterator = lunchMenu.createIterator();
while(iterator.hasNext(){
MenuItem menuItem = iterator.next();
}
하나의 새로운 Iterator 인터페이스를 만들어도 되지만, java.util.Iterator 인터페이스를 사용해서
Iterator를 적용시켜 보자.
Menu.interface
public interface Menu {
public Iterator<MenuItem> createIterator();
}
PancakeHouseMenu.java
public class PancakeHouseMenu implements Menu {
ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
this.menuItems = new ArrayList();
additem("K&B 팬케이크 세트","스크램블드 에그와 토스트가 곁들여진 펜케이크",true,2.99);
additem("레귤러 팬케이크 세트","달걀 후라이와 소시지가 곁들여진 펜케이크",false,2.99);
additem("블루베리 펜케이크","신선한 블루베리와 블루베리 시럽으로 만든 펜케이크",true,3.49);
additem("와플","와플, 취향에 따라 블루베리나 딸기를 얹을 수 있습니다.",true,3.59);
}
public void additem(string name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItem.add(menuItem);
}
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}
@Override
public Iterator<MenuItem> createIterator() {
return menuItems.iterator(); //ArrayList 컬렉션은 반복자를 리턴하는 iterator() 라는 메소드가 있음.
}
//기타 메소드들...
}
DinnerMenu.java
public class DinnerMenu implements Menu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinnerMenu() {
this.menuItems = new MenuItem[MAX_ITEMS];
additem("채식주의자용 BLT","통밀 위에 (식물성)베이컨, 상추, 토마토를 얹은 메뉴",true,2.99);
additem("BLT","통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴",false,2.99);
additem("오늘의 스프","감자 샐러드를 곁들인 오늘의 스프",false,3.29);
additem("핫도그","사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그",false,3.05);
}
public void additem(string name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if(nemberOfItems >= MAX_ITEMS){
System.err.println("죄송합니다, 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems+1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
@Override
public Iterator<MenuItem> createIterator() {
return new DinerMenujIterator(menuItems);
}
//기타 메소드들...
}
DinnerMenuIterator.java
public class DinerMenuIterator implements Iterator<MenuItem> {
Menuitem[] list;
int position = 0;
public DinnerMenuIterator(MenuItem[] list) {
this.list = list;
}
@Override
public MenuItem next() {
MenuItem menuItem = list[position];
position += 1;
return menuItem;
}
@Override
public boolean hasNext() {
if(position >= list.length || list[position] == null) return false;
else return true;
}
@Override
public void remove() { // 반드시 기능을 제공하지 않아도됨 그렇다면 java.lang.UnsupportedOperationException을 던지도록 하면됨
if(position <= 0) Throw new IllegalStateException("next()가 한번도 호출되지 않음.");
if(list[position-1] != null){
for(int i=position-1; i<(list.length-1); i++){
list[i] = list[i+1];
}
list[list.length-1] = null;
}
}
}
Waitress.java
public class Waitress {
ArrayList<Menu> menus;
public Waitress(ArrayList<Menu> menus) {
this.menus = menus;
}
public void printMenu() {
Iterator menuIterator = menus.iterator();
while(menuIterator.hasNext()){
Menu menu = menuIterator.next();
printMenu(menu.createIterator());
}
}
private void printMenu(Iterator<MenuItem> iterator) {
while(iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.println(menuItem.getName());
System.out.println(menuItem.getPrice());
System.out.println(menuItem.getDescription());
}
}
}
MenuTestDrive.java
public class MenuTestDrive {
public static void main(String args[]) {
ArrayList<Menu> menuList = new ArrayList();
menuList.add(new PancakeHouseMenu());
menuList.add(new DinerMenu());
Waitress waitress = new Waitress(menuList);
waitress.printMenu();
}
}
이제 집합체 내에서 어떤식으로 일이 처리되는지에 대해서 전혀 모르는 상태에서도 그 안에 들어있는 모든 항목들에 대해서 반복작업을 수행할 수 있게 되었다.
집합체에서 내부 컬렉션과 관련된 기능과 반복자용 메소드 관련 기능을 전부 구현하도록 했다면 어땠을까?
그렇게 하면 집합체에 들어가는 메소드 개수가 늘어나는데 이게 나쁜걸까?
우선 클래스에서 원래 그 클래스의 역할(집합체 관리) 외에 다른 역할(반복자 메소드)을 처리하도록 하면, 두 가지 이유로 인해 그 클래스가 바뀔 수 있게 된다.
하나는 컬렉션이 어떤 이유로 인해 바뀌게 되면 그 클래스가 바뀌어야 하고, 반복자 관련 기능이 바뀌었을 때도 클래스가 바뀌어야 한다.
이런 이유로 인해 “변경”이라는 주제와 관련된 디자인 원칙이 있다.
디자인 원칙클래스를 바꾸는 이유는 한 가지 뿐이어야 한다. |
클래스를 고치는 것은 최대한 피해야 한다.
코드를 변경하다 보면 많은 문제가 생길 수 있기 때문이다.
그래서 코드를 변경할 만한 이유가 두가지가 되면 그 만큼 그 클래스를 나중에 고쳐야 할 가능성이 커지게 될 뿐 아니라, 디자인에 있어서 두 가지 부분이 동시에 영향을 미치게 된다.
이 원칙에 따르면 한 역할은 한 클래스에서만 맡게 해야 한다.
'Back-End > Design Pattern(디자인 패턴)' 카테고리의 다른 글
퍼사드 패턴(Facade Pattern) 정리 (0) | 2019.06.10 |
---|---|
어댑터 패턴(Adapter Pattern) 정리 (0) | 2019.06.10 |
템플릿 메소드 패턴(Template Method Pattern) 정리 (0) | 2019.06.05 |
싱글톤 패턴(Singleton Pattern) 정리 (0) | 2019.06.05 |
데코레이터 패턴(Decorater Pattern) 정리 (0) | 2019.06.05 |