반복자는 기본 표현(목록, 스택, 트리 등)을 노출하지 않고 컬렉션의 요소를 순회할 수 있는 동작 설계 패턴이다.
반복자로 클라이언트는 단일 반복 인터페이스를 사용하여 비슷한 방식으로 서로 다른 컬렉션의 요소를 검토할 수 있다.
컬렉션은 프로그래밍에서 가장 많이 사용되는 데이터 유형 중 하나이다. 그럼에도 불구하고 컬렉션은 객체 그룹을 위한 컨테이너일 뿐이다.
다양한 유형의 컬렉션
대부분의 컬렉션은 단순 목록에 해당하는 요소를 저장한다. 그러나 이들 중 일부는 스택, 트리, 그래프 및 기타 복잡한 데이터 구조를 기반으로 한다.
그러나 컬렉션이 어떻게 구조화되었든 간에 다른 코드가 한 요소를 사용할 수 있도록 해당 요소에 접근하는 방법을 제공해야 한다. 같은 요소에 계속 접근하지 않고 컬렉션의 각 요소를 살펴볼 수 있는 방법이 있어야 한다.
목록을 기반으로 한 컬렉션이 있다면 이것은 모든 요소를 반복하면 되기 때문에 쉬운 작업처럼 보일 수 있다. 그러나 트리와 같은 복잡한 데이터 구조의 요소를 어떻게 순차적으로 이동할 수 있을 까? DFS, BFS, 랜덤 액세스와 같은 순회 방법들이 필요할 수 있다.
동일한 컬렉션은 여러 가지 방법으로 이동할 수 있다.
컬렉션에 순회 알고리즘을 점점 많이 추가하면 효율적인 데이터 스토리지라는 주요 책임이 점차 흐려진다. 또한 일부 알고리즘은 특정 앱에 맞게 조정될 수 있어 일반 컬렉션 클래스에 포함시키는 것이 이상할 수 있다.
반복자 패턴의 주요 아이디어는 반복자라고 불리는 별도의 객체로 컬렉션의 순회 동작을 추출하는 것이다.
반복자는 다양한 순회 알고리즘을 구현한다. 여러 반복자 객체는 동일한 컬렉션을 동시에 이동할 수 있다.
반복자 객체는 알고리즘 자체를 구현하는 것 외에도 현재 위치, 마지막까지 남아 있는 요소의 수와 같은 모든 통과 세부 사항을 캡슐화한다. 이 때문에 여러 반복자들이 서로 독립적으로 동일한 컬렉션을 동시에 사용할 수 있다.
일반적으로 반복자는 컬렉션의 요소를 가져오는 하나의 기본 방법을 제공한다. 클라이언트는 어떤 것도 반환하지 않을때까지 이 메서드를 계속 실행할 수 있고, 이는 반복자가 모든 요소를 통과했다는 것을 의미한다.
모든 반복자는 동일한 인터페이스로 구현하고, 클라이언트 코드는 적절한 반복자가 있는 한 모든 컬렉션 유형 및 순회 알고리즘과 호환된다. 컬렉션을 순회하는 특별한 방법이 필요한 경우 컬렉션이나 클라이언트를 변경할 필요 없이 새 반복자 클래스를 만들면 된다.
로마를 돌아다니는 다양한 방법들
로마 여행 방법
무작정 임의의 길로 걷기
스마트폰 네비게이터를 사용하여 여행
가이드 고용하여 여행
이러한 옵션은 로마에 있는 다양한 볼거리와 관광지를 따라가는 반복자 역할을 한다.
반복자 인터페이스는 다음 요소 가져오기, 현재 위치 검색, 반복 다시 시작 등의 컬렉션 순회에 필요한 작업을 선언한다.
구체적인 반복자는 컬렉션을 통과하기 위한 특정 알고리즘을 구현한다. 반복자 객체는 자체적으로 통과 진행사항을 추적해야 하고 이를 통해 여러 반복자가 서로 독립적으로 동일한 컬렉션을 이동할 수 있다.
컬렉션 인터페이스는 컬렉션과 호환되는 반복자를 얻기 위한 메서드를 하나 이상 선언한다. 메서드의 반환 형식은 반복자 인터페이스로 선언되어야 구체적인 컬렉션이 다양한 종류의 반복자를 반환할 수 있다.
구체적인 컬렉션은 클라이언트가 요청할 때마다 특정 반복자 클래스의 새 인스턴스를 반환한다. 나머지 컬렉션 코드는 같은 클래스에 있고 다만 이 세부 사항들이 실제 패턴에 중요하지 않아 생략하였다.
클라이언트는 해당 인터페이스를 통해 컬렉션 및 반복자와 함께 작동한다. 이렇게 하면 클라이언트가 구체적인 클래스와 결합되지 않으므로 동일한 클라이언트 코드로 다양한 컬렉션과 반복자를 사용할 수 있다.
일반적으로 클라이언트는 반복자를 직접 만들지 않고 컬렉션에서 가져오는데 클라이언트가 자신의 특수 반복자를 정의할 때는 이것을 사용하는 경우도 있다.
반복자 패턴은 후드 아래에 복잡한 데이터 구조가 있지만, 편리하거나 보안상의 이유로 클라이언트에서 복잡성을 숨기려는 경우 사용하라.
앱 전체에서 순회 코드의 중복을 줄일 때 사용하라.
코드가 다른 데이터 구조를 순회할 수 있거나 이러한 유형의 구조를 미리 알 수 없는 경우 반복자를 사용하라.
SRP
OCP
각 반복기 객체에는 자체 반복 상태가 포함되어 있어 동일한 컬렉션에 대해 병렬로 반복할 수 있다.
동일한 이유로 반복을 지연하고 필요할 때 계속할 수 있다.
당신의 앱이 단순한 컬렉션으로만 작동한다면 패턴을 적용하는 것은 오버엔지니어링이 될 수 있다.
반복자를 사용하는 것은 일부 특수 컬렉션의 요소를 직접 거치는 것보다 덜 효율적일 수 있다.
복잡도: ★★☆
인기: ★★★
사용 예: 이 패턴은 타입스크립트 코드에서 매우 흔하다. 많은 프레임워크와 라이브러리는 컬렉션 순회하는 표준 방법을 제공하기 위해 이를 사용한다.
식별: 반복자는 탐색 방법(다음, 이전)으로 쉽게 인식할 수 있다. 반복자를 사용하는 클라이언트 코드는 순회 중인 컬렉션에 직접 액세스하지 못할 수 있다.
index.ts
// 반복자 디자인 패턴
interface Iterator2<T> {
// 현재 요소를 반환한다.
current(): T;
// 현재 요소 반환 및 다음 요소로 이동한다.
next(): T;
// 현재 요소의 키를 반환한다.
key(): number;
// 현재 위치가 타당한지 판단한다.
valid(): boolean;
// 첫 번째 요소로 반복자를 되돌린다.
rewind(): void;
}
interface Aggregator {
// 외부 반복자를 검색한다.
getIterator(): Iterator2<string>;
}
// 구체적인 반복자들은 다양한 순회 알고리즘들을 실행한다.
// 이 클래스는 항상 현재 통과 위치를 저장한다.
class AlphabeticalOrderIterator implements Iterator2<string> {
private collection: WordsCollection;
// 현재 순회 위치를 저장한다.
private position: number = 0;
// 순회 방향을 가리키는 변수
private reverse: boolean = false;
constructor(collection: WordsCollection, reverse: boolean = false) {
this.collection = collection;
this.reverse = reverse;
if (reverse) {
this.position = collection.getCount() - 1;
}
}
public rewind(): void {
this.position = this.reverse ? this.collection.getCount() - 1 : 0;
}
public current(): string {
return this.collection.getItems()[this.position];
}
public key(): number {
return this.position;
}
public next(): string {
const item = this.collection.getItems()[this.position];
this.position += this.reverse ? -1 : 1;
return item;
}
public valid(): boolean {
if (this.reverse) {
return this.position >= 0;
}
return this.position < this.collection.getCount();
}
}
// 구체적인 컬렉션은 컬렉션 클래스와 호환되고 새로운 반복자 인스턴스를 검색하는
// 하나 이상의 메서드를 제공한다.
class WordsCollection implements Aggregator {
private items: string[] = [];
public getItems(): string[] {
return this.items;
}
public getCount(): number {
return this.items.length;
}
public addItem(item: string): void {
this.items.push(item);
}
public getIterator(): Iterator2<string> {
return new AlphabeticalOrderIterator(this);
}
public getReverseIterator(): Iterator2<string> {
return new AlphabeticalOrderIterator(this, true);
}
}
const collection = new WordsCollection();
collection.addItem('First');
collection.addItem('Second');
collection.addItem('Third');
const iterator = collection.getIterator();
console.log('Straight traversal:');
while (iterator.valid()) {
console.log(iterator.next());
}
console.log('');
console.log('Reverse traversal:');
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
console.log(reverseIterator.next());
}
결과
반복자는 컬렉션의 요소를 순회할 수 있는 객체를 만들어 사용하는 동작 설계 패턴이다.
모든 반복자는 동일한 인터페이스로 구현하여 새로운 순회방법들을 만들 때 쉽게 만들 수 있다.