February
5th,
2025
반복자 패턴 (Iterator Pattern)
문제
컬렉션이 어떻게 구성되어 있는지에 따라 컬렉션은 그 요소들에 접근할 수 있는 방법을 제공해야한다. 순회하는 방법이 있을 것이다. 어떤 경우엔 깊이우선탐색이 되야할 수도 있고 다른 경우엔 너비우선탐색이 되어야 할 수도 있다. 컬렉션에 이러한 순회 알고리즘을 추가할 수록 컬렉션의 주요 책임이 명확해지지 않게 될 수 있다.
해결
반복자라는 별도의 객체로 추출하는 방법이다. 반복자 객체는 알고리즘 자체를 구현하는 것 외에 모든 순회 세부 정보를 캡슐화하여, 여러 반복자들이 서로 독립적으로 동시에 같은 컬렉션을 순회할 수 있다.
일반적으로 반복자는 하나의 주 메소드만 있고, 아무것도 반환하지 않을 때까지 계속 실행되는 형태이다.
컬렉션을 순회하는 특별한 방법이 필요한다면 컬렉션의 변화 없이 새 반복자 클래스를 만들면 된다.
구조
- 반복자 인터페이스는 컬렉션의 순회에 필요한 작업들을 선언한다.
- 구상 반복자들은 컬렉션 순회를 위한 특정 알고리즘을 구현한다.
- 컬렉션 인터페이스는 컬렉션과 호환되는 반복자들을 가져오기 위한 하나 이상의 메소드들을 선언한다.
- 구상 컬렉션은 클라이언트가 요청할 때마다 특정 구상 반복자 클래스의 인스턴스를 반환한다.
- 클라이언트는 반복자들과 컬렉션들의 인터페이스를 통해 작동한다. 클라이언트가 구상 클래스들에 결합하지 않으므로 클라이언트 코드로 다양한 컬렉션들과 반복자들을 사용할 수 있도록 한다.
예시
interface SocialNetwork {
fun createFriendsIterator(val profileId: String): ProfileIterator
fun createCoworkerIterator(val profileId: String): ProfileIterator
}
class Facebook : SocialNetwork {
override fun createFridendsIterator(val profileId: String): ProfileIterator {
return FacebookIterator(this, profileId, "friends")
}
override fun createCoworkerIterator(val profileId: String): ProfileIterator {
return FacebookIterator(this, profileId, "coworkers")
}
}
interface ProfileIterator {
fun getNext(): Profile
fun hasMore(): Boolean
}
class FacebookIterator(
private val facebook: Facebook
private val profileId: String
private val type: String
) : ProfileIterator {
private var currentPosition: Int? = null
private var cache: List<Profile> = null
private fun lazyInit() {
if (cache == null) {
cache = facebook.socialGraphRequest(profileId, type)
}
}
override fun getNext(): Profile {
if (hasMore()) {
return cache[currentPosition++]
}
}
override fun hasMore(): Boolean {
return currentPosition < cache.length
}
}
class SocialSpammer {
fun send(val iterator: ProfileIterator, message: String) {
while(iterator.hasMore()) {
profile = iterator.getNext()
println(profile.getEmail(), message)
}
}
}
fun main() {
val network = Facebook()
val spammer = SocialSpammer()
val iterator = network.createFriendIterator(profile.getId())
spammer.send(iterator, "the message")
}
적용
- 컬렉션이 내부에 복잡한 데이터 구조가 있지만 이 구조의 복잡성을 보안이나 편의성의 이유로 클라이언트에게 숨기고 싶을 때 사용
- 반복자 패턴을 사용해 앱에서 순회 코드의 중복을 줄인다
- 반복자 패턴은 코드가 다른 데이터 구조들을 순회할 수 있기를 원할 때 또는 이러한 구조들의 유형을 미리 알 수 없을 때 사용
장단점
- 단일 책임 원칙
- 개방폐쇄 원칙
- 같은 컬렉션을 병렬로 순회 가능
- 순회를 지연하고 필요할 때 계속할 수 있다
- 단순 컬렉션을 이용하는 경우 과할 수 있다
- 특수 컬렉션들의 요소들을 직접 탐색하는 것보다 덜 효율적일 수 있다