|

코틀린 디자인 패턴 (16) – Iterator 패턴과 sequence는 어떻게 연결될까

코틀린 디자인 패턴 (16) – Iterator 패턴과 sequence는 어떻게 연결될까
Iterator 패턴은 순회 인터페이스를 통일하고, Sequence는 그 순회를 lazy 처리와 연결한다

Iterator 패턴은 디자인 패턴 책에서 보면 꽤 전통적인 주제처럼 느껴집니다. 그런데 코틀린에서는 이 패턴이 낡은 이야기가 아니라, Iterable, iterator(), for 문, Sequence를 이해하는 데 바로 연결되는 살아 있는 감각입니다.

이번 글에서는 Iterator 패턴을 정의 암기로 설명하지 않고, 컬렉션 내부 구조를 감춘 채 같은 방식으로 순회하고 싶은 문제에서 출발해 코틀린 표준 라이브러리와 Sequence까지 연결해보겠습니다.

Iterator 패턴 핵심 카드
이번 글의 핵심 판단 기준을 먼저 잡는 요약 카드

공식 기준은 Kotlin iterators 문서Kotlin sequences 문서를 참고했습니다. 이번 글은 그 내용을 패턴 관점과 코틀린 실전 감각으로 다시 풀어 설명합니다.

Iterator 패턴은 왜 필요할까

컬렉션을 순회할 때 우리가 정말 원하는 것은 내부 자료구조의 구현 세부가 아닙니다. List가 배열 기반인지, 연결 구조인지, 트리인지보다 중요한 것은 원소를 하나씩 꺼내 같은 방식으로 다룰 수 있는가입니다.

Iterator 패턴은 이 지점을 추상화합니다. 사용자는 순회 대상의 내부 구조를 몰라도 되고, 공통된 순회 인터페이스만 알면 됩니다. 즉 패턴의 핵심은 “어떻게 저장돼 있는가”보다 “어떻게 방문할 수 있는가”를 분리하는 데 있습니다.


코틀린에서는 이 패턴이 어디에 보일까

코틀린에서 가장 익숙한 장면은 Iterable과 iterator()입니다. 표면적으로는 그냥 for 문을 쓰는 것 같지만, 그 뒤에는 순회자를 꺼내는 약속이 숨어 있습니다.

val numbers = listOf(1, 2, 3)
val iterator = numbers.iterator()

while (iterator.hasNext()) {
    println(iterator.next())
}

이 코드는 전형적인 Iterator 패턴의 장면입니다. numbers가 내부에서 배열을 쓰든 다른 구조를 쓰든, 사용하는 쪽은 iterator.hasNext()와 iterator.next()만 알면 됩니다.

for 문 내부 순회 흐름 도식
for 문이 내부에서 어떤 순회 약속을 기대하는지 흐름으로 정리한 도식

for 문은 왜 Iterator 패턴과 연결될까

코틀린 공식 문서에도 나오듯이, Iterable 컬렉션에 대해 for 문을 쓰면 iterator를 암묵적으로 얻습니다. 즉 우리는 편한 문법을 쓰고 있지만, 실제 개념은 여전히 순회자 기반입니다.

val numbers = listOf("one", "two", "three")

for (item in numbers) {
    println(item)
}

이 문법이 중요한 이유는, Iterator 패턴이 책 속의 낡은 추상화가 아니라 코틀린의 기본 순회 문법에 이미 녹아 있다는 점을 보여주기 때문입니다. 즉 Iterator 패턴을 이해하면 for 문의 배경도 더 잘 보입니다.


Sequence는 왜 여기서 같이 나올까

여기서 많은 독자가 헷갈립니다. Iterator 패턴과 Sequence는 같은 것일까요? 완전히 같지는 않습니다. Iterator 패턴은 순회 인터페이스를 통일하는 문제에 더 가깝고, Sequence는 그 순회를 lazy 처리와 연결하는 방식에 더 가깝습니다.

즉 Iterator 패턴이 “원소를 하나씩 방문한다”는 공통 통로를 만든다면, Sequence는 “그 방문을 언제 실제로 계산할 것인가”를 더 세밀하게 다루는 쪽입니다.

Iterator 패턴과 Sequence 역할 차이 비교 카드
둘을 같은 개념으로 보면 헷갈리고, 역할 차이로 보면 훨씬 또렷해진다

Iterable과 Sequence는 무엇이 다를까

여기서 중요한 것은 eager와 lazy를 성능 신화처럼 외우지 않는 것입니다. eager는 각 단계를 바로 수행하면서 중간 결과를 만들어 가는 쪽이고, lazy는 정말 필요해질 때까지 계산을 미루는 쪽입니다. 둘 중 무엇이 더 좋은지는 데이터 크기와 처리 체인 길이에 따라 달라집니다.

Sequence를 언제 검토할지 실전 판단 카드
Sequence를 쓰면 좋은 경우와 굳이 과하게 쓰지 않아도 되는 경우를 정리한 카드
eager와 lazy 오해 비교 카드
Sequence를 무조건 최적화 도구처럼 보면 왜 오해가 생기는지 비교한 카드

Kotlin 공식 문서 기준으로 일반 Iterable 체인은 보통 eager하게 처리됩니다. 즉 map, filter 같은 단계가 순서대로 실행되면서 중간 결과 컬렉션이 생길 수 있습니다. 반면 Sequence는 가능한 한 lazy하게 흘러갑니다.

val result = listOf(1, 2, 3, 4, 5)
    .asSequence()
    .filter { it % 2 == 1 }
    .map { it * 2 }
    .toList()

이 코드는 결과가 실제로 필요해질 때까지 계산을 미루고, 원소 하나씩 filter와 map을 통과시키는 흐름으로 볼 수 있습니다. 그래서 긴 처리 체인이나 큰 데이터에서는 유리할 수 있습니다.

Iterable과 Sequence 차이 비교 카드
eager 처리와 lazy 처리 감각을 비교한 카드

다만 여기서 Sequence가 항상 더 빠르다고 단정하면 안 됩니다. 공식 문서도 lazy 처리에는 오버헤드가 있을 수 있다고 말합니다. 작은 컬렉션이나 단순 연산에서는 일반 컬렉션 체인이 더 단순하고 충분할 수 있습니다.



ListIterator와 MutableIterator를 보면 더 분명해진다

코틀린 공식 문서 기준으로 리스트에는 ListIterator가 있고, mutable 컬렉션에는 MutableIterator가 있습니다. 이 점은 Iterator 패턴이 단순히 한 종류의 순회자만 있다는 뜻이 아니라, 기본 순회 인터페이스 위에 더 구체적인 책임이 확장될 수 있음을 보여줍니다.

ListIterator는 양방향 순회처럼 리스트에 특화된 기능을 보여주고, MutableIterator는 순회 중 변경이라는 더 조심스러운 책임을 드러냅니다. 즉 Iterator 패턴은 단순한 next 호출을 넘어서, 순회 책임을 어떤 수준까지 열어둘지 설계하는 문제와도 닿아 있습니다.

ListIterator와 MutableIterator 확장 포인트 비교 카드
같은 순회자 계열이라도 책임이 어떻게 확장되는지 비교한 카드

실전에서는 어떻게 판단하면 좋을까

  1. 순회 추상화 자체를 이해하고 싶다면 Iterator 패턴부터 본다
  2. for 문, iterator(), Iterable 관계를 이해하면 코틀린 기본 순회가 훨씬 선명해진다
  3. 여러 연산을 이어 붙일 때 중간 컬렉션 생성을 줄이고 싶다면 Sequence를 검토한다
  4. 하지만 작은 데이터나 단순 처리에서는 Sequence를 과하게 쓰지 않아도 된다

예를 들어 단순한 리스트 한 번 순회는 Sequence까지 갈 필요가 없는 경우가 많습니다. 반면 필터, 매핑, take 같은 연산이 길게 이어지고 데이터가 커질수록 Sequence의 장점이 더 잘 보일 수 있습니다.


코틀린다운 포인트는 무엇일까

전통적인 디자인 패턴 책에서는 Iterator를 별도 참여 객체와 UML로 설명하는 경우가 많습니다. 하지만 코틀린에서는 패턴의 핵심 아이디어가 언어와 표준 라이브러리 안에 자연스럽게 녹아 있어, 훨씬 덜 무겁게 만날 수 있습니다.

이 점이 바로 시리즈 전체에서 반복해서 보게 되는 코틀린다운 포인트입니다. 패턴이 사라진 것이 아니라, 더 언어 친화적인 형태로 흡수된 것입니다. Iterator도 그 대표적인 예입니다.

이런 관점은 앞선 Command 패턴 글에서 코틀린이 실행 단위 구조를 어떻게 더 가볍게 표현하는지 본 흐름과도 이어집니다. 또 직전 15편 Interpreter 패턴 글처럼, 언어와 라이브러리가 전통 패턴의 무게를 어떻게 줄여 주는지 비교해 보면 시리즈 흐름이 더 잘 보입니다.


마무리

Iterator 패턴은 컬렉션 내부 구조를 숨긴 채 공통된 방식으로 순회하고 싶다는 오래된 문제를 해결합니다. 코틀린에서는 이 아이디어가 Iterable, iterator(), for 문 안에 이미 자연스럽게 스며들어 있습니다.

그리고 Sequence는 그 순회 감각을 lazy 처리까지 확장해서, 여러 연산을 더 유연하게 이어 가게 해 줍니다. 즉 Iterator 패턴은 순회 추상화의 뼈대이고, Sequence는 그 뼈대 위에 올라간 코틀린다운 처리 전략으로 이해하면 훨씬 덜 헷갈립니다.

함께보면 좋은 글