스택큐힙리스트

Iterator 패턴 – 컬렉션 속을 우아하게 누비는 비결 본문

개발

Iterator 패턴 – 컬렉션 속을 우아하게 누비는 비결

스택큐힙리스트 2025. 7. 29. 07:53
반응형

“for-each 없이도, 내부 구조 몰라도, 끝까지 한 번에!”


왜 Iterator인가?

코드가 커질수록 자료구조가 제각각입니다. 배열, 연결 리스트, 트리, 그래프…
Iterator 패턴은 데이터 구조를 감추고 “다음 요소를 주세요” 라는 단일 프로토콜로 순회(Traversal)를 통일합니다.
즉, 컬렉션 구현을 몰라도 같은 방식으로 next() / hasNext()만 호출하면 끝. 책임이 깨끗하게 나뉘니 테스트·유지보수·확장성이 모두 좋아집니다.


핵심 아이디어

  1. Iterable(집합) – “Iterator를 하나 주세요!”를 요청받으면 해당 컬렉션에 맞는 반복자를 반환
  2. Iterator(반복자) – 순회 상태를 보유하며 hasNext()와 next()를 통해 한 걸음씩 이동
  3. 클라이언트 – 내부 구조를 몰라도 동일한 API로 데이터에 접근

Kotlin 예제 – 깊이 우선 트리 순회

 
// 트리 노드
data class Node<T>(val value: T, val children: List<Node<T>> = emptyList())

// Iterator 구현
class DepthFirstIterator<T>(start: Node<T>) : Iterator<T> {
    private val stack = ArrayDeque<Node<T>>().apply { push(start) }

    override fun hasNext() = stack.isNotEmpty()
    override fun next(): T {
        val node = stack.removeLast()
        node.children.reversed().forEach { stack.addLast(it) } // DFS
        return node.value
    }
}

// Iterable 래퍼
class Tree<T>(private val root: Node<T>) : Iterable<T> {
    override fun iterator(): Iterator<T> = DepthFirstIterator(root)
}

// 사용
fun main() {
    val tree = Tree(
        Node(1, listOf(Node(2), Node(3, listOf(Node(4), Node(5)))))
    )
    for (v in tree) print("$v ")      // 1 2 3 4 5
}
  • 자료구조 비공개: Tree 외부에서는 스택·자식 리스트를 전혀 알 필요가 없습니다.
  • 전략 교체: 필요하면 BreadthFirstIterator만 새로 만들어 넣으면 끝 – OCP 준수.

언제 빛을 발하나?

  • 다양한 컬렉션 타입을 일괄 처리할 때 (e.g., 레거시 리스트 + 새 큐 혼재)
  • 복잡한 순회 로직을 캡슐화해 비즈니스 코드에서 분리하고 싶을 때
  • 동일 컬렉션에 여러 순회 방식(정방향·역방향·필터링)을 런타임에 바꿔야 할 때

실전 팁

  • Kotlin/JVM에선 Iterable·Sequence가 이미 Iterator 패턴의 정석 구현이므로 직접 구현보다 확장(커스텀 iterator()·asSequence()) 방식이 간단합니다.
  • 병렬 처리가 필요하면, 순회 상태를 공유하지 않는 클론 가능한 Iterator를 따로 두어야 Race Condition을 피할 수 있습니다.
  • “한 번만 순회해야” 하는 스트림(예: 네트워크 응답)은 forEachRemaining 대신 코루틴 Flow를 고려해 I/O 차단을 줄일 수 있습니다.
반응형
Comments