이터레이터 패턴이란
정말 단순하게 말하면 접근자를 만드는 패턴 정도로 생각하면 된다.
배열의 경우 그냥 인덱스로 접근하면 그만이지만 복잡한 데이터 구조의 경우(트리, 그래프 등) 순회를 하기에 어려운점이 있는데 이때 사용한다.
iterator를 사용하는 이유가 뭘까?
교과서적인 목적을 말해보자면 데이터 집합체의 내부 구조를 외부에 노출하지 않고 원소들을 순차적으로 검색하기 위해서 사용한다고 한다.
위에서도 봤지만 다시 한번 보자
Iterator패턴을 구현하려면 순환접근 하고자 하는 클래스가 iterable이라는 인터페이스를 구현해야 한다. 여기에서는 Aggregate로 되어있다.
또한 아까 말했던 접근 하고자 하는 클래스는 ConcreteAggregate를 말한다.
여기서 iterator를 생성한다.
iterator는 이 iterable이 구현된 코드에서만 접근이 가능하다.
아래 코드는 java를 이용해 만든 코드이다.
interface Iterator<E> {
public boolean hasNext();
public E next();
}
//Aggregate
interface Iterable<E> {
public Iterator<E> iterator();
}
class Book {
private String name = "";
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//ConcreteAggregate클래스
class BookShelf implements Iterable<Book> {
private Book[] books;//Data
private int last = 0;//book의 개수를 파악하기 위한 변수
public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
@Override
public Iterator<Book> iterator() {
//iterator의 생성
return new BookShelfIterator(this);
}
}
//ConcreteIterator
class BookShelfIterator implements Iterator<Book> {
private BookShelf bookShelf; //책장 생성
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
} else {
return false;
}
}
@Override
public Book next() {
if(!hasNext()){
throw new NoSuchElementException();
}
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
public class Main {
public static void main(String[] args) {
//책장 생성
BookShelf bookShelf = new BookShelf(4);
//책추가
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
//iterator생성
Iterator<Book> it = bookShelf.iterator();
//내부 data출력
while (it.hasNext()) {
Book book = it.next();//next()메소드는 현재 book을 반환한다.
System.out.println(book.getName());
}
}
}
이 코드는 책과 책장을 만들고 책장의 요소를 반환하는 iterator를 만들었다.
↳ 예제를 클래스 다이어그램으로 표현Aggregate가 Iterable 이고
ConcreteAggregate가 BookShelf,
BookShelfIterator가 ConcreteIterator 이다.
스위프트로 구현해보자 하는데 스위프트는 인터페이스가 없다
그래서 비슷한 기능을 하는 프로토콜을 사용해서 만들었다
그리고 스위프트의 경우 프로토콜에서 제네릭을 사용하려면 associatedtype키워드를 사용해야 한다 위의 java코드와 거의 동일한 역할을 하니 참고 바란다.
// Iterator 프로토콜 정의
protocol Iterator {
associatedtype Element
func hasNext() -> Bool
func next() -> Element
}
// Aggregate 프로토콜 정의
protocol Aggregate {
associatedtype IteratorType: Iterator
func iterator() -> IteratorType
}
// ConcreteAggregate 클래스 구현
class ConcreteAggregate<Element>: Aggregate {
private var arr: [Element] = []
init() {}
// 요소 추가 메서드
func add(_ element: Element) {
arr.append(element)
}
// Iterator 반환 메서드
func iterator() -> ConcreteIterator<Element> {
return ConcreteIterator(aggregate: self)
}
// 내부 요소 접근 메서드
fileprivate func getElement(at index: Int) -> Element {
return arr[index]
}
// 요소 수 반환 메서드
fileprivate var count: Int {
return arr.count
}
}
// ConcreteIterator 클래스 구현
class ConcreteIterator<Element>: Iterator {
private let aggregate: ConcreteAggregate<Element>
private var currentIndex: Int = 0
init(aggregate: ConcreteAggregate<Element>) {
self.aggregate = aggregate
}
func hasNext() -> Bool {
return currentIndex < aggregate.count
}
func next() -> Element {
let element = aggregate.getElement(at: currentIndex)
currentIndex += 1
return element
}
}
// 사용 예시
// 문자열을 다루는 ConcreteAggregate와 ConcreteIterator
let stringAggregate = ConcreteAggregate<String>()
stringAggregate.add("Swift")
stringAggregate.add("Iterator")
stringAggregate.add("Pattern")
let stringIterator = stringAggregate.iterator()
print("문자열 컬렉션 순회:")
while stringIterator.hasNext() {
let element = stringIterator.next()
print(element)
}
이번시간에는 iterator패턴에 대해서 알아보았다.
iterator패턴은 사용자 정의 데이터에 접근하기 위해서 많이 사용하는 패턴으로 접근하는 데이터를 담는 클래스는 iterable인터페이스를 구현하는 형태여야 한다.