Iterator[Design Pattern]

SnowCat·2023년 3월 20일
0

Design Pattern

목록 보기
16/23
post-thumbnail

의도

  • 반복자 -> 리스트, 스택, 트리 등의 컬렉션 요소들으 기본 뵤현을 노출하지 않고 순회할 수 있도록 해주는 행동 디자인 패턴

문제

  • 객체 그룹의 집합인 컬렉션이 있다 가정해보자.
  • 보통 컬렉션들을 저장하는 곳은 배열이지만, 일부는 스택, 트리, 그래프 등의 조금 더 복잡한 자료 구조에 저장되기도 한다.
  • 자료 구조에 상관없이 컬렉션은 객체에 접근할 수 있는 방법을 제공해야 한다.
  • 배열이나 스택과 같은 경우 간단하게 요소 순회가 가능하지만, 트리의 경우를 가정하면 DFS, BFS등의 여러 방식의 순회방식을 고려할 수 있다
  • 이 과정에서 순회 알고리즘이 많아질수록 컬렉션의 주요 책임은 명확해지지 않게 된다.
  • 또한 클라이언트는 순회 방식에 관심을 가질 필요가 없음에도 불구하고 클라이언트 코드가 어떤 방식으로 순회를 할지 알려줘야 하기 때문에 컬렉션 클래스와 클라이언트 클래스가 결합되버리는 문제가 생기게 된다.

해결책

  • 컬렉션의 순회 동작을 반복자라는 별도의 객체로 추출
  • 반복자에는 순회 알고리즘과 함께 현재 위치, 남은 객체들의 수와 같은 순회에 필요한 세부 정보들을 함께 저장함 => 이를 통해 여러 반복자들이 독립적으로 같은 컬렉션을 통과할 수 있게 됨
  • 반복자에서는 컬렉션의 요소를 가져오기 위한 주 메서드를 제공하고, 반복자가 순회가능한 동안 클라이언트가 이 메서드를 실행함
  • 모든 반복자들은 같은 인터페이스를 구현해 클라이언트 코드에서 모든 반복자에 접근할 수 있도록 해야 함

구조

/*
 * 반복자 인터페이스
 * 여기서는 현개값, 다음 값, 객체의 key, position의 타당성 여부, 초기화 ㄱ     
 */

interface Iterator<T> {
    current(): T;
    next(): T;
    key(): number;
    valid(): boolean;
    rewind(): void;
}

/* 
 * 반복자를 가져오기 위한 컬렉션 인터페이스
 * 컬렉션 인터페이스에서는 반복자를 가져오기 위한 하나 이상의 메서드를 선언하게 됨
 */ 
interface Aggregator {
    getIterator(): Iterator<string>;
}

/**
 * 인터페이스에 맞춰서 구체적인 반복자 객체들을 하나씩 생성
 */

class AlphabeticalOrderIterator implements Iterator<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() {
        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(): Iterator<string> {
        return new AlphabeticalOrderIterator(this);
    }

    public getReverseIterator(): Iterator<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());
}
/*
First
Second
Third
*/

console.log('');
console.log('Reverse traversal:');
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
    console.log(reverseIterator.next());
}
/*
Third
Second
First
*/

적용

  • 컬렉션이 복잡한 데이터 구조로 이루어져 있지만, 구조의 복잡성을 숨기고 싶을 때 사용
    반복자 패턴을 통해 데이터 구조와 작업을 캡슐화 할 수 있음
  • 앱 전체에서 순회 코드의 중복을 피하고 싶을 때 사용
    알고리즘의 코드는 부피가 매우 크기 때문에 비즈니스 로직에 통합해 사용하는 것이 효율적임
  • 여러 순회 방식을 사용해야 하거나, 어떤 순회 방식을 사용해야할지 모를 때 사용
    반복자 패턴과 컬렉션들은 각각 인터페이스가 호환되어 있음

구현 방법

  1. 반복자 인터페이스 선언, 이 때 컬렉션에서 다음 요소를 가져오는 메서드를 반드시 가져야 하고, 순회에 필요한 메서드들을 추가할 수 있음
  2. 컬렉션 인터페이스를 선언하고 반복자를 가져오는 메서드를 구현
    컬렉션 인터페이스의 반환 타입은 반복자 인터페이스와 같아야 함
  3. 반복자들이 순회하게 할 수 있도록 하고 싶은 컬렉션에 대해 구상 반복자 클래스 구현
  4. 컬렉션 클래스에서 컬렉션 인터페이스를 구현해 클라이언트가 반복자들을 생성할 수 있도록 해줌
  5. 클라이언트 코드에서 컬렉션 순회 코드를 반복자 객체를 사용하는 방식으로 조정

장단점

  • 부피가 큰 순회 알고리즘들을 별도의 클래스로 추출해 단일 책임 원칙 준수
  • 새로운 컬렉션과 반복자들을 코드 변경없이 코드에 추가할 수 있게 되어 개방, 폐쇄 원칙 준수
  • 같은 컬렉션을 병렬로 순회할 수 있으며, 순회의 중단, 계속을 구현할 수 있음
  • 앱의 컬렉션이 단순한 경우 패턴을 굳이 적용할 필요가 없음
  • 반복자를 사용하는 것이 반드시 효율적인 결과는 아닐 수 있음
profile
냐아아아아아아아아앙

0개의 댓글