Iterator Pattern 이터레이터 패턴을 액션스크립트로 컨버팅 – Head First Design Pattern
by 세계의끝 on 11.24, 2009, under Design Pattern

이런 것은 optical(시각적인) iterator pattern 이라 할 수 있겠죠.
9장의 내용은 이터레이터 패턴과 컴포지트 패턴을 식당 통합 메뉴를 구현하는 연속되는 내용으로 설명하고 있는데요, 이 한 개 챕터의 페이지가 다른 부분보다 훨씬 많은 70페이지인데다가 설명해야 하는 부분도 많고 코드도 복잡하기 때문에 이터레이터와 컴포지트를 2개의 포스트로 나누어서 작성하도록 하겠습니다.
이터레이터 패턴의 정의는 다음과 같습니다.
“이터레이터 패턴은 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공합니다.”
즉, 반복을 패턴화 한다는 이야긴데요. 어떤식으로 구현하는지, 그리고 단순히 반복문을 사용하는 것과는 어떤 점이 다른지 알아보도록 하겠습니다.
객체마을 식당과 팬케이크 하우스가 합병하게 되어 메뉴를 통합해야 하는 상황입니다. 문제는 메뉴를 구현하는 방식을 놓고 양쪽의 오너들은 자신의 코드 수정을 꺼려하고 있는 것이죠. 먼저 메뉴를 어떤 식으로 구현하기로 했는지 보도록 할까요?
0 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 31 32 33 34 35 36 37 | package { public class MenuItem { private var name:String; private var description:String; private var vegetarian:Boolean; private var price:uint; public function MenuItem( $name:String, $description:String, $vegetarian:Boolean, $price:uint ) { this.name = $name; this.description = $description; this.vegetarian = $vegetarian; this.price = $price; } public function getName():String { return name; } public function getDescription():String { return description; } public function getPrice():uint { return price; } public function isVegetarian():Boolean { return vegetarian; } } } |
특별히 어려운 부분은 없는것 같습니다. 메뉴 이름, 메뉴 설명, 채식주의자용 메뉴여부, 가격의 네 가지 변수를 가져오는 getter 메서드 들이 있고, 생성자에서 객체를 만들면서 해당 변수에 대입합니다.
양쪽의 식당은 이 메뉴구현에 맞추어 각자의 메뉴를 입력하면 됩니다만 기존에 사용하던 메뉴 구조가 서로 다릅니다. 먼저 짚고 넘어갈 부분은 책 본문에서는 양쪽의 형식이 다르다는 것을 표현하기 위해 자바의 ArrayList 와 Array 를 사용했지만, 액션스크립트에 ArrayList 는 없기 때문에 이번에 FlashPlayer 10에서 지원하게 된 Vector 로 바꿔서 코드를 번역하였습니다. 또한 원래 DinerHouse 의 메뉴가 배열원소의 갯수를 제한한 Array 였기 때문에[01] DinerHouse 의 Array 를 원소 갯수를 제한할 수 있는 Vector 로 바꾸고, PancakeHouse 의 ArrayList 를 Array 로 구현하였습니다.
그럼 PancakeHouse 의 메뉴를 먼저 보겠습니다.
0 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 31 32 33 34 35 36 | package { public class PancakeHouseMenu implements IMenu { private var menuItems:Array; public function PancakeHouseMenu () { menuItems = new Array() addItem( "K&B 팬케이크 세트", "스크램블드 에그와 토스트가 곁들여진 팬케이크", true, 2990 ) addItem( "레귤러 팬케이크 세트", "달걀 후라이와 소시지가 곁들여진 팬케이크", false, 2990 ) addItem( "블루베리 팬케이크", "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크", true, 3490 ) addItem( "와플", "와플, 취향에 따라 블루베리나 딸기를 얹을 수 있습니다.", true, 3590 ) } public function addItem( $name:String, $description:String, $vegetarian:Boolean, $price:uint ):void { var menuItem:MenuItem = new MenuItem( $name, $description, $vegetarian, $price ) menuItems.push(menuItem); } /* * 이터레이터 패턴 구현으로 사용하지 않게된 메서드 public function getMenuItems():MenuItem { return menuItems; } */ public function createIterator():IIterator { return new PancakeHouseIterator( menuItems ) } //기타 메뉴관련 메서드 } } |
위에서 언급한대로 Array 를 이용하여 메뉴를 구현하고 있습니다.
그럼 DinerHouse 의 메뉴구현은 어떤가요?
0 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package { public class DinerMenu implements IMenu { private static const MAX_ITEM:uint = 6; private var numberOfItems:uint = 0; private var menuItems:Vector.<MenuItem>; public function DinerMenu () { menuItems = new Vector.<MenuItem>( MAX_ITEM, true ) addItem( "채식주의자용 BLT", "통밀 위에 (식물성)베이컨, 상추, 토마토를 얹은 메뉴", true, 2990 ) addItem( "BLT", "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴", false, 2990 ) addItem( "오늘의 스프", "감자 샐러드를 곁들인 오늘의 스프", false, 3290 ) addItem( "핫도그", "사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그", false, 3050 ) } public function addItem( $name:String, $description:String, $vegetarian:Boolean, $price:uint ):void { var menuItem:MenuItem = new MenuItem( $name, $description, $vegetarian, $price ) if ( numberOfItems >= MAX_ITEM ) { trace( "죄송합니다, 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다." ) } else { menuItems[numberOfItems] = menuItem; numberOfItems++ } } /* * 이터레이터 패턴 구현으로 사용하지 않게된 메서드 public function getMenuItems():MenuItem { return menuItems; } */ public function createIterator():IIterator { return new DinerMenuIterator( menuItems ) } //기타 메뉴관련 메서드 } } |
팬케익하우스보다 약간 복잡하지만 Vector 를 사용했기 때문에 전체 메뉴의 갯수가 6개로 제한되어 있다는것 외에는 딱히 다른점은 없습니다.
그럼 이제 이터레이터 인터페이스를 보겠습니다.
0 1 2 3 4 5 6 7 | package { public interface IIterator { function hasNext():Boolean; function next():Object; } } |
다음 수행할 항목이 있는지 알아보는 hasNext() 메서드와 다음 항목을 리턴해주는 next()메서드가 있습니다.
팬케익하우스 이터레이터가 위의 인터페이스를 어떻게 구현했나 살펴볼까요?
0 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 31 32 33 34 | package { public class PancakeHouseIterator implements IIterator { private var items:Array; private var position:int = 0; //생성자에서는 메뉴에서 넘어온 모든 메뉴를 한꺼번에 받음 public function PancakeHouseIterator ( $items:Array ) { this.items = $items; } //다음 메뉴 배열원소를 리턴 public function next():Object { var memuItem:MenuItem = items[position]; position++; return memuItem; } //다음 메뉴가 있는지 확인하고 Boolean을 리턴 public function hasNext():Boolean { if ( position >= items.length ) { return false; } else { return true; } } } } |
다음은 디너하우스의 이터레이터 입니다. MenuItem 을 Vector 에 저장하고 있습니다.
0 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 31 32 | package { public class DinerMenuIterator implements IIterator { private var items:Vector.<MenuItem>; private var position:int = 0; public function DinerMenuIterator ( $items:Vector.<MenuItem> ) { this.items = $items; } public function next():Object { var memuItem:MenuItem = items[position]; position++; return memuItem; } public function hasNext():Boolean { //Vector 의 경우 원소가 없는 것은 null 이기 때문에 null 도 검사 if ( position >= items.length || items[position] == null ) { return false; } else { return true; } } } } |
그럼 실제 메뉴를 찍어줄 웨이트리스를 만나보겠습니다.
0 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 31 32 33 34 35 | package { public class Waitress { private var pancakeHouseMenu:IMenu; private var dinerMenu:IMenu; //웨이트리스는 생성자에서 양쪽 식당의 메뉴를 받아 변수에 저장 public function Waitress( $pancakeHouseMenu:IMenu, $dinerMenu:IMenu ) { this.pancakeHouseMenu = $pancakeHouseMenu; this.dinerMenu = $dinerMenu; } //이터레이터 객체를 준비 public function printMenu():void { var pancakeIterator:IIterator = pancakeHouseMenu.createIterator(); var dinerIterator:IIterator = dinerMenu.createIterator(); trace( "\n메뉴n----\n아침메뉴" ); printItMenu( pancakeIterator ); trace( "\n메뉴n----\n점심메뉴" ); printItMenu( dinerIterator ); } //실제 개별 메뉴를 찍어줌 private function printItMenu( $iterator:IIterator ):void { while ( $iterator.hasNext() ) { var menuItem:MenuItem = MenuItem( $iterator.next() ); trace( menuItem.getName(), ", ", menuItem.getPrice(), "--", menuItem.getDescription() ); } } } } |
웨이트리스 코드에서는 각메뉴의 createIterator() 를 호출하여 이터레이터 객체를 얻고 MenuItem 의 갯수만큼 반복하여 메뉴를 얻습니다.
호스트코드에서는 웨이트리스 객체를 만들어 팬케익하우스와 디너하우스의 메뉴를 인자로 던져 모든 메뉴를 출력합니다.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { var pancakeHouseMenu:PancakeHouseMenu = new PancakeHouseMenu(); var dinerMenu:DinerMenu = new DinerMenu(); var waitress:Waitress = new Waitress( pancakeHouseMenu, dinerMenu ); waitress.printMenu(); } } } |
출력된 모든 메뉴 입니다.
메뉴
—-
아침메뉴
K&B 팬케이크 세트 , 2990 — 스크램블드 에그와 토스트가 곁들여진 팬케이크
레귤러 팬케이크 세트 , 2990 — 달걀 후라이와 소시지가 곁들여진 팬케이크
블루베리 팬케이크 , 3490 — 신선한 블루베리와 블루베리 시럽으로 만든 팬케이크
와플 , 3590 — 와플, 취향에 따라 블루베리나 딸기를 얹을 수 있습니다.
메뉴
—-
점심메뉴
채식주의자용 BLT , 2990 — 통밀 위에 (식물성)베이컨, 상추, 토마토를 얹은 메뉴
BLT , 2990 — 통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴
오늘의 스프 , 3290 — 감자 샐러드를 곁들인 오늘의 스프
핫도그 , 3050 — 사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그
일견 왜 이렇게 복잡하게 반복을 수행해야하나 하는 생각이 들 수 있겠습니다. 책의 359페이지에 따르면 메뉴 항목을 구현하는 방식의 차이[02] 때문에 항상 다른형식 메뉴판 숫자만큼의 for 문이 필요하게 되고, 웨이트리스가 채식주의자용 메뉴만 출력하는 메서드를 구현해야한다면 다른 메서드에서 계속 반복적인 코드를 추가해야 하고, 게다가 구현방식이 다른 다른 식당 메뉴를 더 구현해야 한다면 또다시 모든 메서드에 반복문을 하나씩 더 추가해야 하는데, 이터레이터 패턴으로 구조를 잘 짜 놓으면 이러한 경우에 생길 수 있는 에러를 미연에 방지하고 유지보수성도 좋아지게 되는 것입니다.
그러나 이터레이터는 이러한 상이한 형식을 통합하여 반복시키는것 이상의 훨씬 추상적인 작업도 수행할 수 있습니다. 이터레이터 패턴을 자유자재로 구현하게 되면 반복이 될 것 같지 않은 요소들을 반복시킬 수 있는 길이 열리게 되는 것이죠. 이터레이터 패턴을 다루는 다른 글을 포스팅 하게 되면 이 패턴을 사용하는 진짜 이유에 대해서도 살펴보도록 하겠습니다.
Iterator Pattern Example 액션스크립트 코드 다운로드 -
이 샘플코드는 Vector 클래스를 사용했기 때문에 Flash Player 10 으로 컴파일 해야 합니다.
2009년 5월 27일 초안 작성 / 2009 11월 24일 약간 수정하여 출판
Blog under the Creative Commons Attribution-NoDerivs 3.0 License
11월 25th, 2009 on am 9:26
잘보고 갑니다^6
저도 예전에 한번 해서 포스팅 해야겠다고 생각은 했는데, .
깔끔하게 정리 해주셨네요^^
11월 25th, 2009 on am 10:02
많이 부족합니다. ^^
야매코더님이 포스팅 하시면 훨씬 더 정리가 잘될것 같네요.
11월 26th, 2009 on pm 11:57
항상 좋은 자료 감사합니다.
11월 27th, 2009 on am 12:16
좀 더 자주 포스팅 하고 싶은데, 마음같이 안되네요. ^^
12월 22nd, 2009 on pm 3:38
와~~ 대단하십니다.
12월 22nd, 2009 on pm 3:39
혹시 플래쉬 스터디 같은거 하시나요?…
저는 웹개발자인데..ㅠㅠ 플래쉬도 해야하는데..ㅋㅋ
그래서 대충은 책보고 통밥으로 하려고 하는데… 맘처럼 쉽지 않네요.^^
12월 22nd, 2009 on pm 10:55
네 PFG 스터디와 다른곳의 비 정기정인 스터디도 참여하고 있습니다.
혼자 공부하는것도 필요하지만 다른사람이 어떻게 코딩을 하는지 구경하면 많이 배우게 되죠.