프로그래밍 Design Pattern 이해하기 - 9 이터레이터 패턴 & 컴포지트 패턴 <1>


IT Story/프로그래밍이야기 2014.09.22 10:40



컬렉션을 잘 관리하자! - 이터레이터 패턴 & 컴포지트 패턴


컬렉션에는 굉장히 많은 객체들이 들어있다. 그리고 종류도 다양하다. List, Hashtable, ArrayList 등등...

'루'와 '멜'은 저녁 요리사와 아침 팬케이크 요리사이다. 그들은, 둘이 메뉴를 합쳐 점심식사를 제공하기로 했다. 

다음 MenuItem 클래스를 보자!



 위 클래스는 MenuItem Contructor로 이름과 설명, 가격, 채소를 먹는지 여부에 대해 넣을 수 있으며, get함수를 통해 각각의 값을 가져올 수 있다. 


 위 클래스를 이용해서 아래와 같이 PancakeHouse 와 DinnerMenu 를 만들어보자. 




PancakeHouseMenu.java


DinnerMenu.java 


위 두 클래스를 보자.


PancakeHouseMenu.java를 보면, ArrayList ( ArrayList<MenuItem> ) 처음에 만든 MenuItem을 가지고 있는 Array를 만들어서 addItem을 하고 있다. DinnerMenu.java에서는, 같은 방식으로 ArrayList에 MenuItem을 추가하고 있지만, 다른 점은 Max count가 존재해 Array에 Item을 추가하기 전에 주문의 수가 6개인지 체크를 하고 있다. 


 웨이터를 고용했다! (웨이터의 이름은 Alice ) 웨이터라고 지칭하고 있지만 이를 대신 할 Java class를 만들어보자. 


이 웨이터가 가지고 있어야 할 함수는 

printMenu() - 모든메뉴를 보여준다. 

printBreakfastMenu() - 아침 메뉴만 보여준다. 

printLunchMenu() - 점심 메뉴만 보여준다. 

printVegiterianMenu() - 채소 메뉴만 보여준다. 

isItemVegetarian(name) - 메뉴의 이름을 넣으면, 해당 메뉴가 채소만 들어가 있는지 확인한다. 


자 그럼 printMenu()부터 구현해보자. 


1. 모든 메뉴들을 보여주되 방식은 pancakeHouseMenu 와 DinnerMenu 에게 menuItem들을 달라고 요청한다!


pancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();

ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();


DinerMenu dinerMenu = new DinerMenu();

MenuItem[] lunchItems = dinerMenu.getMenuItems();


2. 그런 뒤 위의 코드처럼, 아침 메뉴와 점심 메뉴를 println 으로 출력을 하도록 만들었다. 






여기까지가 기존방식의 코드 

--------------------------------------------------------------

아래부터 Iterator를 사용한 코드 


그렇다면, 어떻게 이터레이션(반복)되는 것을 캡슐화 할 수 있을까? 

우리는 이 스터디를 통해 배운 한가지가 있다. 바로 캡슐화 하는 것이다. 자 다음을 따라가 보자. 


1. 아침 메뉴들을 가져오는 문에 get을 통해 가져오자. 


for (int i = 0; i < breakfastItems.size(); i++){

MenuItem menuItem = (MenuItem) breakfastItems.get(i);

}


2. 점심 메뉴 역시 같은 방식으로 가져온다. 


for (int i = 0; i < lunchItems.length; i++){

MenuItem menuItem = lunchItems[i];

}


3. Iterator를 사용해 가져와 보자 


Iterator iterator = breakfastMenu.createIterator();


while(iterator.hasNext()){

MenuItem menuItem = (MenuItem) iterator.next();

}


Iterator iterator = lunchMenu.createIterator();


while(iterator.hasNext()){

MenuItem menuItem = (MenuItem) iterator.next();

}


자 그러면 이제 이터레이터를 어떻게 캡슐화하는지 살펴보자. 첫번째로 당신이 알아야 할 것은 이터레이터라고 불리는 것은 interface라는 것이다. (실제 위에서 사용한 것처럼 세부 클래스를 작성해보자!)





Iterator는 interface로 hasNext()와 next()를 가지고 있다. 

그리고 이를 상속받아 DinnerMenuIterator 등을 만들 수 있다. 




public interface Iterator{

boolean hasNext();

Object next();

}


이를 이용해서 Dinner menu를 만들어보자!


public class DinerMenuIterator implements Iterator {

MenuItem[] items;

int position = 0;


public DinnerMenuIterator(MenuItem[] item){

this.items = items;

}


public Object next() {

MenuItem menuItem = items[position];

position = position + 1;

return menuItem;

}


public boolean hasNext() {

if(position >= tems.length || items[position] == null)

return false;

else

return true;

}

}


Iterator에 적어놓은 "다음 아이템이 있는지" 여부와 "다음 아이템"을 가져오는 메서드를 작성한다. 


public class DinnerMenu{

static final int MAX_ITEMS = 6;

int numberOfItems = 0;

MenuItem[] menuItems;


public Iterator createIterator(){

return new DinerMenuIterator(menuItems);

}

}


위에서 만들어놓은 DinnerMenuIterator를 이용한 DinnerMenu 클래스이다. 

이를 응용해서 아까 만들어놓은 waitress code를 변경해보자. 


public class Waitress {

PancakeHouseMenu pancakeHouseMenu;

DinerMenu dinerMenu;


public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu){

this.pancakeHouseMenu = pancakeHouseMenu;

this.dinerMenu = dinerMenu;

}


public void printMenu(){

Iterator pancakeIterator = pancakeHouseMenu.createIterator();

Iterator dinerIterator = dinerMenu.createIterator();

System.out.println("MENU===== breakfast");

printMenu(pancakeIterator);

System.out.println("MENU===== lunch");

printMenu(dinerIterator);

}



public void printMenu(Iterator iterator){

while( iterator.hasNext() ){

MenuItem menuItem = (MenuItem) iterator.next();

System.out.print(menuItem.getName() + ", ");

System.out.print(menuItem.getPrice() + " -- ");

System.out.print(menuItem.getDescription() );

}

}

}


Waitless 클래스를 사용하는 Test Drive 코드는 아래와 같다. 


public class MenuTestDrive {

public static void main(String args[]){

PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();

DinerMenu dinerMenu = new DinerMenu();


Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);


waitress.printMenu();

}

}




이전 방식 - MenuItem을 가지고 ArrayList를 만들어 직접 출력하는 것 

의 좋지 못한 점으로는,


- 캡슐화가 되지 않았다.

- 두개의 루프문을 돌아야 한다. 

- 웨이트리스는 MenuItem[]과 ArrayList 두가지를 bind한다. 

- 웨이터리스는 각각 다른 메뉴클래스(Concreate class)를 가져야 한다. 


그에 비해 Iterator를 사용하면, 


- Menu에 대해 캡슐화가 된다. 웨이트리스는 메뉴에 대해 알고 있을 필요가 없다 

 Iterator를 통해 haxNext()와 next()를 이용해 보여주기만 하면 된다 (첫 예제는 직접 MenuItem에서 pancakeHouseMenu.getMenuItems()를 호출하고 그런 뒤에 직접 ArrayList를 루프를 통해 출력을 했다. 하지만 Iterator를 쓴 예제에서는, 그냥 hasNext()와 next()만을 사용할 뿐 직접 루프를 돌면 getMenu() getPrice() 등 자세한 내용을 알고 있을 필요가 없다)

- 우리는 모든 루프에 대해 컬렉션(Iterator를 implements한)이 알아서 처리하도록 만들 수 있다!

- 웨이트리는 ArrayList, MenuItem 등 다양한 컬렉션을 bind하지 않고, interface(Iterator)를 이용한다 

- 팬케이크와 디너의 공통된 부분을 Interface로 뽑아서 사용하게 된다. (수정이 용이하다)


Iterator pattern은 

반복자 패턴은 기본 표현을 노출시키지 않고 순차적으로 집계 오브젝트의 요소에 액세스 할 수있는 방법을 제공한다





자바에서는 java.util.Iterator을 통해 next(), hasNext(), remove() 를 제공한다. 

Iterator에는 internal Iterator와 external Iterator가 존재한다.

External Iterator

When you get an iterator and step over it, that is an external iterator


for (Iterator iter = var.iterator(); iter.hasNext(); ) {
  Object obj = iter.next();
  // Operate on obj
}

Internal Iterator

When you pass a function object to a method to run over a list, that is an internal iterator


var.each( new Functor() {
  public void operate(Object arg) {
    arg *= 2;
  }
});

 즉 위에서 우리가 한 패턴은 External Iterator를 사용한다. Internal의 경우 자바에서, 


public class IterationExamples {
    public static void main(String[] args){
        List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
         
        alphabets.forEach(l -> l.toUpperCase());
    }
}


위와 같이 사용되며, 차이점으로는 external Itertation은 "What"과 "How"에 대해 접근을 하지만, internal iteration은 "what"에 대해서만 궁금해할 때 사용이 되며, "how( external 에서는 while( OO.hasNext()) <~ 이 부분을 의미)"에 대해서는 foreach가 처리한다는 것이 차이점이다. 


안드로이드에서 작업을 하다보면, ArrayList 를 사용해 서버에서 받은 자료를 처리 할 때 ConcurrentModificationException이 발생하는 경우가 있다. 

해당 컬렉션을 변경하는 중에 add, remove를 하는 경우 발생하는 익셉션인데, (syncronized 문제) 

이런 경우에 Iterator를 사용했었다.


Iterator 패턴은 "SIngle Responsibility" 에 부합한다. 이전에도 나왔듯이, 단일 책임 원칙이란 이론으로, 해당 Iterator concreate class 는 해당 컬렉션을 관리하는 책임만 한다는 점에서 부합한다. 





양이 많아 두개의 포스팅으로 나뉘어 진행합니다. 다음 포스팅(컴포지트 패턴) 을 참조해주세요~!






저작자 표시 비영리 변경 금지
신고

WRITTEN BY
ShakeJ

0 ,