Iterator 패턴
Iterator 패턴을 구현해서 서로 다른 자료구조를 동일한 방식으로 순회하는 방법을 알려드려요. 이 패턴으로 자료구조가 달라도 클라이언트 코드를 수정하지 않고 일관된 방식으로 데이터에 접근할 수 있어요.
Iterator 패턴이 왜 필요한가요?
실무에서 이런 상황을 자주 마주칠 수 있어요. 두 개발팀이 비슷한 기능을 각각 개발했는데, 한 팀은 배열을 사용하고 다른 팀은 연결 리스트를 사용했어요. 이제 두 데이터를 합쳐서 보여줘야 하는데 문제가 생겨요.
A팀 - Array로 자료 구현 | B팀 - 연결 리스트로 구현 |
|---|
class ArrayInventory { constructor() { this.items = []; } addItem(item) { this.items.push(item); } getItems() { return this.items; }}
| class Node { constructor(data) { this.data = data; this.next = null; }}class LinkedListInventory { constructor() { this.head = null; } addItem(item) { const newNode = new Node(item); if (!this.head) { this.head = newNode; return; } let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } getHead() { return this.head; }}
|
Client - 두 팀의 Item Collection을 합쳐서 출력 |
|---|
// 두 팀의 아이템들을 합쳐서 출력해야 함const arrayInventory = new ArrayInventory();arrayInventory.addItem({ name: '노트북', quantity: 10 });arrayInventory.addItem({ name: '마우스', quantity: 50 });const listInventory = new LinkedListInventory();listInventory.addItem({ name: '키보드', quantity: 30 });listInventory.addItem({ name: '모니터', quantity: 15 });// 두 팀의 아이탬 출력 방식의 차이로 비슷한 코드의 반복function displayAllItems() { const arrayItems = arrayInventory.getItems(); for (let i = 0; i < arrayItems.length; i++) { // A팀 재고 출력 console.log(`${arrayItems[i].name}: ${arrayItems[i].quantity}개`); } let current = listInventory.getHead(); while (current) { // B팀 재고 출력 console.log(`${current.data.name}: ${current.data.quantity}개`); current = current.next; }}
|
위 Client 코드의 문제점이 보이시나요?
문제점
배열과 연결 리스트를 순회하는 코드가 완전히 달라요
새로운 자료구조(트리, 스택 등)가 추가되면 또 다른 순회 코드를 작성해야 해요
클라이언트 코드가 각 자료구조의 내부 구현을 알아야 해요
코드 중복이 많고 유지보수가 어려워요
그러면 위와 같은 문제점을 어떻게 해결할 수 있을까요? 바로 Iterator 패턴을 사용하면 Collection을 같은 인터페이스로 순회하게 만들어 Client는 각 팀의 내부 구현에 대해서 알 필요가 없어져요. 그리고 새로운 방식의 Collection으로 C팀이 추가 구현을 해도 Iterator 인터페이스 방식에 따라 사용할 수 있게되면 마찬가지로 내부 구현을 알 필요가 없어져요.
Iterator 패턴으로 해결하기
Iterator 패턴을 적용하면 자료구조가 달라도 동일한 방식으로 순회할 수 있어요.
A팀 이터레이터 | B팀 이터레이터 |
|---|
// 배열용 Iteratorclass ArrayIterator extends Iterator { constructor(items) { super(); this.items = items; this.index = 0; } hasNext() { return this.index < this.items.length; } next() { return this.items[this.index++]; }}class ArrayInventory { createIterator() { // 이터레이터 제공 메서드 return new ArrayIterator(this.items); }}
| // 연결 리스트용 Iteratorclass LinkedListIterator extends Iterator { constructor(head) { super(); this.current = head; } hasNext() { return this.current !== null; } next() { const data = this.current.data; this.current = this.current.next; return data; }}class LinkedListInventory { createIterator() { // 이터레이터 제공 메서드 return new LinkedListIterator(this.head); }}
|
Client - Iterator 적용한 코드 |
|---|
function displayAllItemsWithIterator() { const arrayInventory = new ArrayInventory(); arrayInventory.addItem({ name: '노트북', quantity: 10 }); arrayInventory.addItem({ name: '마우스', quantity: 50 }); const listInventory = new LinkedListInventory(); listInventory.addItem({ name: '키보드', quantity: 30 }); listInventory.addItem({ name: '모니터', quantity: 15 }); // 배열이든 연결 리스트든 동일한 코드로 순회! (반복되는 비슷한 코드가 사라짐) const inventories = [arrayInventory, listInventory]; inventories.forEach((inventory, idx) => { console.log(`\n=== 재고 ${idx + 1} ===`); const iterator = inventory.createIterator(); while (iterator.hasNext()) { const item = iterator.next(); console.log(`${item.name}: ${item.quantity}개`); } });}displayAllItemsWithIterator();
|
어때요? Client는 Iterator 인터페이스만 알아야하니 각 팀이 어떤 자료형을 썼는지 전혀 알 필요가 없어졌습니다.
Iterator 패턴 클래스 다이어그램
Iterator 패턴의 클래스 다이어그램이 어떻게 되는지 보여드릴게요.

client는 createInterator() 함수를 구현하는 Aggregate이라는 공통된 인터페이스를 통해 객체 컬렉션의 구현과 분리될 수 있어요. 그리고 Iterator 인터페이스만을 사용해 Collection을 순회할 수 있게 됩니다.
Iterator 패턴 vs 직접 접근 비교
구분 | Iterator 패턴 | 직접 접근 |
|---|
코드 통일성 | 모든 자료구조를 동일한 방식으로 순회 | 자료구조마다 다른 코드 필요 |
유지보수 | 새 자료구조 추가 시 기존 코드 수정 불필요 | 모든 사용처를 찾아 수정 필요 |
캡슐화 | 내부 구조 감춤 | 내부 구조 노출 |
확장성 | 새 Iterator만 추가하면 됨 | 조건문이 계속 늘어남 |
마무리
이렇게 Iterator(반복자) 패턴을 통해서 서로 다른 자료구조인 Collection을 통일된 인터페이스로 다룰 수 있게 만들어 봤습니다. 이렇게 함으로써 내부 구조를 알 필요가 없어지니 캡슐화가 되고, 유지보수 관점에서도 새로운 자료형이 추가되도 Iterator만 추가하고 Client 코드를 고치지 않아도 되니 OCP 원칙도 잘 따르게 됩니다. 그러면 항상 Collection을 다룰때 패턴을 사용하면 될까여?? 아니요 단일 자료구조만 사용한다거나, 순회 로직이 한 곳만 있다던가, 아니면 내부 반복자(internal iterator[ex. map, foreach])로 충분히 처리 가능하면 필요없습니다. 아무튼 Iterator 패턴의 진정한 가치는 서로 다른 자료구조를 통일된 인터페이스로 다룰 수 있게 된다는 점이에요. 이를 통해 자료구조의 변경에 영향받지 않고, 확장도 쉬워집니다.