Iterator Pattern 에 대하여

Iterator Pattern 에 대하여

이터레이터 패턴은 이터레이터 객체를 활용하여 컬렉션의 요소들을

순차적으로 접근할 수 있도록 설계된 디자인 패턴입니다.

이를 통해 배열, 리스트, 해시맵 등 다양한 자료구조와 상관없이

일관된 인터페이스를 사용하여 요소를 순회할 수 있게 됩니다.

Iterator Pattern

const m = new Map();

m.set('name', 'do-not-do-that');
m.set('age', 65);
m.set('city', 'seoul');


const s = new Set();

s.add(10);
s.add(20);
s.add(30);

for (let item of m) console.log(item);
for (let item of s) console.log(item);

/**
['name', 'do-not-do-that']
['age', 65]
['city', 'seoul']

10
20
30
*/

Map 과 Set 은 서로 다른 자료구조이지만, 이터러블(Iterable) 프로토콜을 따르기 때문에 for ... of 문을 사용하여 동일한 방식으로 순회할 수 있습니다.

Map 의 경우 [key, value] 형태의 배열을 반환하며, Set은 단일 값을 반환하는 구조를 가집니다.

이처럼 다양한 컬렉션이 일관된 이터레이터 인터페이스를 구현하고 있어,
내부 구조와 상관없이 동일한 방식으로 순회할 수 있다는 것이 이터레이터 패턴의 핵심 장점입니다.

용어 정리 ✍🏻

이터레이터 VS 이터러블

  • 이터레이터란, {value: 값, done: true / false} 형태의 이터레이터 객체를 리턴하는 next() 메서드를 가진 객체입니다.
  • 이터러블이란, 이터레이터를 리턴하는 [Symbol.iterator]() 메서드를 가진 객체입니다.

JavaScript 에서의 이터레이터 패턴 구현

JS에서는 이터레이터 패턴을 두 가지 방식으로 구현합니다.

  • 커스텀 이터레이터 구현
  • 제너레이터 함수

하나씩 직접 코드로 살펴봅시다.

기본 이터레이터 구현

JavaScript에서 이터레이터는 next() 메서드를 구현하는 객체입니다.

이 메서드는 순회할 값과 done 상태를 반환하는 객체를 리턴해야 합니다.

class CustomIterator {
  constructor(data) {
    this.index = 0;
    this.data = data;
  }

  next() {
    if(this.index < this.data.length) {
      return { value: this.data[this.index++], done: false };
    }else {
       return { value: undefined, done: true } 
    }
  }
}

// 실제 사용
const iterable = [10, 20, 30, 40];
const iterator = new CustomIterator(iterable);

console.log(iterater.next()); // { value: 10, done: false }
console.log(iterater.next()); // { value: 20, done: false }
console.log(iterater.next()); // { value: 30, done: false }
console.log(iterater.next()); // { value: 40, done: false }
console.log(iterater.next()); // { value: undefined, done: true }

  • CustomIterator 클래스는 배열을 순회할 수 있는 커스텀 이터레이터 입니다.
  • next() 메서드를 호출하면 다음 요소를 반환하며, 더 순회할 요소가 없으면 done:true 를 반환합니다.
  • for...of 문에서 사용할 수는 없으며, next() 를 직접 호출해야합니다.

이터러블 프로토콜을 구현한 객체 만들기

위의 예제는 이터레이터 프로토콜만 구현했기 때문에, for...of 문을 사용할 수 없습니다.

만약 이터러블 프로토콜을 구현하면 이터레이터 객체를 for...of 문과 함께 사용할 수 있습니다.

const customIterable = {
  data = ["apple", "banana", "cherry"],
  index = 0,

  [Symbol.iterator]() {
    return {
      data: this.data,
      index: this.index,
      next() {
        if (this.index < this.data.length) {
          return { value: this.data[this.index++], done: false };
        }else {
          return { value: undefined, done: true };
        }
      }
    }
  }
}

for (const fruit of customIterable) {
  console.log(fruit);
}

// apple
// banana
// cherry

  • [Symbol.iterator]() 메서드를 추가해 이터러블 프로토콜을 구현했습니다.
  • 이터러블 프로토콜이 구현되었기 때문에, for...of 문에서 사용할 수 있는 것을 확인할 수 있습니다.
  • next() 를 직접 호출하는 방식보다 좀 더 가독성이 좋아졌습니다.

좀 더 재밌는걸 해보겠습니다.

const arr = ['home', 'sweet', 'candy'];

for (const favorite of arr) {
  console.log(favorite);
}

// home
// sweet
// candy

위 코드를 실행시키면, 자연스럽게 arr 배열 내의 모든 요소들을 순회하며 출력합니다.

const arr = ['home', 'sweet', 'candy'];
arr[Symbol.iterator] = null;

for (const favorite of arr) {
  console.log(favorite);
}

이제 위와 같이 Symbol.iterator 를 의도적으로 null 로 변환시키면,

이터러블 객체가 아니기 때문에, 더이상 for...of 로 순회할 수 없음을 볼 수 있습니다.

제너레이터를 활용한 이터레이터

제너레이터 함수는 이터레이터 패턴을 구현할 때 훨씬 간결한 코드를 만들 수 있습니다.

제너레이터는 yield 키워드를 사용해 값을 하나씩 반환하는 함수입니다.

function* numberGenerator(){
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

const generator = numberGenerater();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: 4, done: false }
console.log(generator.next()); // { value: undefined, done: true }
  • function* 문법을 사용하면 제너레이터 함수를 정의할 수 있습니다.
  • yield 키워드를 사용해 값을 하나씩 반환합니다.
  • next() 를 호출하면 다음 yield 지점까지 실행된 후 중단됩니다.

마무리

이터레이터 패턴은 요소를 순차적으로 접근할 수 있도록 해주는 디자인 패턴입니다.

이를 통해 배열, 객체 등 다양한 컬렉션을 일관된 방식으로 순회할 수 있습니다.

비록 저는 실무에서 이터레이터 패턴을 직접 구현해서 사용하는 경우를 보지는 못했으나,

JavaScript의 Map, Set, for...of 등이 기본적으로 이터레이터 패턴을 따르고 있다는 점을 생각하면 우리가 이미 자연스럽게 사용하고 있다고 볼 수 있겠습니다.

이 글이 도움이 되었길 바라며, 읽어주셔서 감사합니다!

Reference

Iterator Design Pattern in Javascript (& Typescript)
the iterator pattern is a design pattern in which an iterator is used to traverse a container and access….
[JS] 📚 이터러블 & 이터레이터 - 💯완벽 이해
이터러블(interable) 이터러블(interable)이란 자료를 반복할 수 있는 객체를 말하는 것이다. 우리가 흔히 쓰는 배열 역시 이터러블 객체이다. 그럼 만일 이 배열에게 이터러블 표식을 없애버리면 어떻게 될까? let arr = [1,2,3] for(const a of arr) console.log (a) // 정상작동 1,2,3 arr[Symbol.iterator] = null; // 이렇게 하면 순회가 되지 않는다 for(const a of arr) console.log (a) // Uncaught TypeError: arr is not iterable 멀쩡한 배열임에 불구하고 for..of로 순회할수가 없게 되었다. 저 [Symbol.iterator] 라는 코드가 눈에 띈다. 이제 저것이…