컬렉션의 구현에 관계 없이 Client가 탐색할 수 있는 방법 제공을 하는 것을 목표로 하는 행동 디자인 패턴이다.
컬렉션이란?

자바에서 "컬렉션(Collection)"은 여러 객체(요소)를 모아 처리할 수 있는 인터페이스와 클래스의 집합을 의미한다. 컬렉션 프레임워크는 데이터를 저장, 검색, 수정, 삭제 등을 효율적으로 관리하기 위한 구조적인 방법을 제공한다.
다음은 List와 Queue들에 대해서 원소들을 순회하는 방식이다.
List의 순회방식
import java.util.ArrayList;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Apple");
stringList.add("Banana");
stringList.add("Cherry");
for (int i = 0; i < stringList.size(); i++) {
System.out.println(stringList.get(i));
}
}
}
Queue의 순회방식
import java.util.LinkedList;
import java.util.Queue;
public class QueueExampleWithoutIterator {
public static void main(String[] args) {
Queue<String> stringQueue = new LinkedList<>();
stringQueue.add("Apple");
stringQueue.add("Banana");
stringQueue.add("Cherry");
while (!stringQueue.isEmpty()) {
String element = stringQueue.poll();
System.out.println(element);
}
System.out.println("Queue after iteration is empty: " + stringQueue.isEmpty());
}
}
만약 Client가 List를 이용하는 데이터를 이용하다가 해당 자료구조가 Queue로 바뀌게 된다면 위와 같이 관련 모든 코드를 수정해야할 것이다. 이터레이터 패턴은 이러한 Collection내의 항목들에 순차적으로 접근하는 방식을 추상화해서 Client에 제공하여 컬렉션의 구현과 관계없이 객체를 탐색할 수 있게 해준다.
주요 목적
- 추상화 증진 : 컬렉션의 구현방식에 상관없이 요소에 접근하는 일관된 방식을 제공한다.
- 캡슐화 강화 : 내부에서 컬렉션의 구현 방식을 숨긴체로 외부에서 원소에 접근할 수 있게 해준다.
내부 반복자 vs 외부 반복자
내부 반복자
- 이터레이터가 순회에 대한 컨트롤을 완전히 가지게 된다.
- Client는 반복되는 항목에 대해 수행할 작업을 제공한다.
- 코드가 간결해지고 명확해지는 장점을 가짐.
자바 예시
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
public class InternalIteratorExample {
public static void main(String[] args) {
List<String> stringList = Arrays.asList("Apple", "Banana", "Cherry");
Set<Integer> integerSet = new HashSet<>(Arrays.asList(1, 2, 3));
// List에서 forEach와 람다 표현식을 사용
System.out.println("List Elements:");
stringList.forEach(element -> System.out.println(element));
// Set에서 forEach와 람다 표현식을 사용
System.out.println("Set Elements:");
integerSet.forEach(element -> System.out.println(element));
// 메서드 참조를 사용하는 경우 (더 간결한 표현)
System.out.println("List Elements with Method Reference:");
stringList.forEach(System.out::println);
}
}
Go 예시
package main
import "fmt"
func main() {
// List 생성 (slice 사용)
list := []string{"apple", "banana", "cherry"}
// List 순회
fmt.Println("List (Slice):")
for index, value := range list {
fmt.Printf("Index: %d, Value: %s\n", index, value)
}
// Set 생성 (map 사용)
set := make(map[string]bool)
// Set에 값 추가
set["apple"] = true
set["banana"] = true
set["cherry"] = true
// Set 순회
fmt.Println("\nSet (Map):")
for key := range set {
fmt.Printf("Key: %s\n", key)
}
}
외부 반복자
- 반복자의 인터페이스를 직접 사용해서 흐름을 제어
- 복잡한 조건을 처리하거나 특정 부분만 순회하는 등의 세밀한 제어를 필요할 때 유용하다.
Java 예시
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class ExternalIteratorExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
Set<Integer> integerSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
// List에서 Iterator를 사용하여 순회 및 조건에 따른 요소 제거
System.out.println("List before: " + stringList);
Iterator<String> listIterator = stringList.iterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
if ("Banana".equals(element)) {
listIterator.remove(); // 조건을 만족하는 요소 제거
}
}
System.out.println("List after removal: " + stringList);
// Set에서 Iterator를 사용하여 순회
System.out.println("Set Elements:");
Iterator<Integer> setIterator = integerSet.iterator();
while (setIterator.hasNext()) {
Integer element = setIterator.next();
System.out.println(element);
}
}
}
Go 예시
package main
import "fmt"
// 슬라이스를 위한 외부 반복자 함수
func iterateSlice(slice []string, action func(int, string)) {
for i, v := range slice {
action(i, v)
}
}
// 맵을 위한 외부 반복자 함수
func iterateMap(m map[string]bool, action func(string, bool)) {
for k, v := range m {
action(k, v)
}
}
func main() {
// 슬라이스 생성
fruits := []string{"apple", "banana", "cherry"}
// 맵 생성
fruitSet := map[string]bool{"apple": true, "banana": true, "cherry": true}
// 슬라이스 순회
fmt.Println("Iterating over slice:")
iterateSlice(fruits, func(index int, value string) {
fmt.Printf("Index: %d, Value: %s\n", index, value)
})
// 맵 순회
fmt.Println("\nIterating over map:")
iterateMap(fruitSet, func(key string, value bool) {
fmt.Printf("Key: %s, Value: %t\n", key, value)
})
}
Iterator 패턴의 단점
- 내부 상태 관리: Iterator는 자신의 내부 상태를 관리해야 하고 이는 추가적인 메모리 사용과 복잡성을 초래할 수 있다.
- 불변성 부재: Iterator를 사용하여 컬렉션을 순회하는 동안, 그 컬렉션을 수정하면 예상치 못한 결과나 오류가 발생할 수 있다. 예를 들어, 순회 중인 컬렉션에서 요소를 제거하면 일부 Iterator 구현에서는 ConcurrentModificationException과 같은 오류가 발생할 수 있다.
- 한 방향 순회: 대부분의 Iterator 구현은 순방향으로만 요소를 순회할 수 있음. 이는 특정 시나리오에서는 제한적일 수 있으며, 역방향 순회나 랜덤 액세스가 필요한 경우 다른 접근 방식을 고려해야함.
- 효율성 문제: 특정 구현에서는 Iterator의 사용이 비효율적일 수 있음. 예를 들어, 매우 큰 컬렉션을 순회할 때 Iterator 객체의 생성과 관리로 인한 오버헤드가 성능에 부정적인 영향을 미칠 수 있음.
'Computer Science > 디자인패턴' 카테고리의 다른 글
Singleton Pattern과 Inversion of Control (1) | 2024.05.06 |
---|---|
[디자인 패턴] 템플릿 메서드 패턴 (1) | 2024.03.31 |
[디자인 패턴] 어댑터 패턴 (0) | 2024.03.23 |
[디자인 패턴] 커맨드 패턴 (2) | 2024.03.18 |
[디자인 패턴] 팩토리 패턴 (0) | 2024.03.09 |