[디자인패턴]Iterator
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
for 문의 i++에서 i를 하나씩 증가시키면, 현재 주목하는 요소는 ‘다음’, ‘그 다음’으로 차례차례 진행된다. 여기에서 사용되는 변수 i의 기능을 추상화하여 일반화한 것을 디자인 패턴에서는 Iterator 패턴이라고 한다.
Iterator 패턴을 사용하는 이유는 구현과 분리하여 반복할 수 있기 때문이다.
while (it.hasNext()){
Book book = it.next();
System.out.println(book.getName());
}
여기에서 사용한 것은 hasNext와 next라는 Iterator의 메소드 뿐이다. BookShelf 구현에 사용된 메소드는 호출되지 않는다. 결국 위 코드에서 while 루프는 BookShelf에 의존하지 않는다.
BookShelf를 구현한 사람이 배열로 책을 관리하는 것을 그만두고, java.util.ArrayList를 사용하도록 프로그램을 변경했다고 하자. BookShelf를 어떻게 변경하든 BookShelf가 iterator 메소드를 가지고 있고 올바른 Iterator<Book>
을 반환하면 위의 while 루프는 변경하지 않아도 동작한다.
예제
클래스 및 인터페이스 목록
이름 | 설명 |
---|---|
Iterable < E > | 집합체를 나타내는 인터페이스(java.lang 패키지) 예제 프로그램에서는 Iterable < Book >으로 사용 |
Iterator < E > | 처리를 반복하는 반복자를 나타내는 인터페이스(java.util 패키지) 예제 프로그램에서는 Iterator < Book >으로 사용 |
Book | 책을 나타내는 클래스 |
BookShelf | 책장을 나타내는 클래스 |
BookShelfIterator | 책장을 검색하는 클래스 |
Main | 동작 테스트용 클래스 |
1. Iterable 인터페이스
Iterable<E>
인터페이스는 처리를 반복할 대상을 나타내는 것으로, java.lang 패키지에 선언되어 있다. 이 인터페이스를 구현하는 클래스는 배열처럼 ‘뭔가 많이 모여 있는것’ 이른바 ‘집합체’가 된다. iterable 이라는 영어 단어는 ‘반복할 수 있다.’, ‘반복 가능’ 이라는 뜻이다.
Iterable<E>
에서 E는 타입 파라미터라는 것으로, 여기에 ‘모여 있는 것’을 나타내는 타입을 지정한다. 예제 프로그램에서는 Book을 모은 인터페이스를 사용하므로, Iterable<Book>
형태롤 사용하게 된다.
public interface Iterable<E>{
public abstract Iterator<E> iterator();
}
Iterable<E>
인터페이스에는 iterator 메소드가 선언되어 있다. 이 메소드는 집합체에 대응하는 Iterator<E>
를 만들기 위한 것이다. 집함체에 포함된 요소를 하나하나 처리해 나가고 싶을 때는 이 iterator 메소드를 사용해 Iterator<E>
인터페이스를 구현한 클래스의 인스턴스를 하나 만든다.
2. Iterator 인터페이스
Iterator<E>
인터페이스는 하나하나의 요소처리를 반복하기 위한 것으로 루프 변수와 같은 역할을 한다.
public interface Iterator<E>{
public abstract boolean hasNext();
public abstract E next();
}
여기에 선언된 메소드는 2가지 이며, ‘다음 요소’가 존재하는지 알아보는 hasNext() 메소드와 ‘다음 요소’를 가져오는 next 메소드이다.
hasNext 메소드는 다음 요소가 존재한다면 이 메소드는 true를 반환한다. 다음 요소가 존재하지 않으면, 즉 마지막 요소까지 이미 도달했다면 이 메소드의 반환 값을 false가 된다.
next 메소드는 반환값 형태가 파라미터 E(Element, 요소)인 것에서 알 수 있듯이, next 메소드는 집합체의 요소를 1개 반환한다. 다음 next 메소드를 호출할 때 제대로 다음 요소를 반환할 수 있도록 내부 상태를 다음으로 진행시켜 놓는 역할이 뒤에 수어 있다. ‘뒤에 숨어 있다’고 해도, Iterator<E>
인터페이스에서는 메소드 이름만 알 수 있다. 구체적인 동작은 Iterator<E>
인터페이스를 구현하는 클래스 BookSHelfIterator 에서 알 수 있다.
3. Book 클래스
책을 나타내는 클래스이며, 책 이름을 getName 메소드로 얻을 수 있다. 책 이름은 생성자에서 인스턴스를 초기화할 때 인수로 지정한다.
public class Book{
private String name;
public Book(String name){
this.name = name;
}
public String getName(){
return name;
}
}
4. BookShelf 클래스
책장을 나타내는 클래스로, 집합체로 다루기 위해 Iterable<Book>
인터페이스를 구현하고 있다. 소스 코드에서 'implements Iterable<Book>'
부분이 Iterable<Book>
인터페이스를 구현하고 있음을 나타낸다. 또한 Iterable<Book>
인터페이스에서 선언되어 있던 iterator 메소드의 실체가 적혀 있는 것도 확인할 수 있다.
iterator 메소드 앞에 @override 라고 적혀 있다. 이는 iterator 메소드가 Iterable 인터체이스에서 선언된 메소드를 오버라이드하여 구현한 것이다. @Override는 부가 정보(어노테이션) 의 일종으로 다른 클래스타 인터페이스와의 관련에서 중요한 역할을 담당하므로 소스 코드를 읽을 때 주의 깊게 봐야 한다.
import java.util.Iterator;
public class BookShelf implements Iterable<Book>{
private Book[] books;
private int last = 0;
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(){
return new BookShelfItertor(this);
}
}
Book 배열의 크기는 처음에 BookShelf 인스턴스를 만들 때 인수(maxsize)로 지정한다. books 필드를 private로 지정한 이유는 이 클래스 밖에서 직접 접근하는 것을 방지하기 위해서 이다.
그 다음에 살펴볼 것은 iterator 메소드다. 이 메소드는 BookShelf 클래스에 대응하는 Iterator로서, BookShelfIterator 클래스의 인스턴스를 생성하여 반환한다. 책장에 꽂혀 있는 책을 반복해서 처리하고 싶을 때 iterator 메소드를 호출한다.
BookShelfIterator 클래스
import java.util.Iterator;
import java.util.NoSuchElementException;
public 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;
}
}
BookShelfIterator는 Iterator<Book>
인터페이스를 구현하고 있으므로 Iterator<Book>
형으로 다룰 수 있다. bookShelf 필드는 BookShelfIterator 가 검색할 책장이고, index 필드는 현재 보고 있고 있는 책을 가리키는 첨자이다.
생성자에서는 전달된 BookShelf 인스턴스를 bookShelf 필드에 저장하고 index를 0으로 지정한다.
hasNext 메소드는 Iterator<Book>
인터페이스에서 선언된 메소들르 구현한 것이다. ‘다음 책’이 있는지 조사해서, 있으면 true 없으면 false를 반환한다. 다음 책이 있는지 없는지는 index가 책장에 꽃힌 책의 수(bookSelf.getLength() 값) 보다 작은지 비교해서 판정한다.
next 메소드는 현재 주목하는 책(Book의 인스턴스)을 반환하고, 다시 ‘다음’으로 진행시키는 메소드이다. 이 메소드도 Iterator<Book>
인터페이스로 선언되어 있는데 조금 복잡하다. 먼저 반환값으로 돌려 줄 책을 book 변수에 저장해 두고, index를 다음으로 진행시킨 후 book을 return 한다. ‘index를 다음으로 진행’하는 처리는 for 문의 i++에 해당하는 처리이다. 루프 변수를 ‘다음’으로 진행한 것이다.
5. Main 클래스
import java.util.Iterator;
public class Main {
public static void main(String[] args){
BookShelf bookShelf = new BookShelf(4);
bookShelf.appnedBook(new Book("Around the World in 80 Days"));
bookShelf.appnedBook(new Book("Bible"));
bookShelf.appnedBook(new Book("Cinderella"));
bookShelf.appnedBook(new Book("Daddy-Long-Legs"));
//1. 명시적으로 Iterator 를 사용하는 방법
Iterator<Book> it = bookShelf.iterator();
while (it.hasNext()){
Book book = it.next();
System.out.println(book.getName());
}
System.out.println();
//2. 확장 for문을 사용하는 방법
for (Book book: bookShelf){
System.out.println(book.getName();)
}
System.out.println();
}
}
첫 번째는 ‘명시적으로 Iterator를 사용하는 방법’ 이다. bookShelf.iterator()로 얻은 it이 책장을 검색할 때 사용할 Iterator<Book>
의 인스턴스이다. while문 조건에 it.hasNext()라고 쓰면, 검색하지 않는 책이 남아 있는 한 while 문의 루프가 돌아간다. 그리고 루프 안에서 it.next()로 책을 가져와 표시한다.
두 번째는 ‘확장 for문을 사용하는 방법’ 이다. 확장 for문은 직전의 while문과 완전히 같은 동작을 한다. 즉, 확장 for문을 사용하면 Iterator를 사용한 반복 처리를 간결하게 기술할 수 있다. 일반적으로 Java의 확장 for문은 Iterable 인터페이스를 구현한 클래스의 인스턴스에 대해 내부적으로 Iterator를 사용하여 처리한다. 결국 Java의 확장 for문 배후에서는 Iterator 패턴이 사용된다고 볼 수 있다.
단어 정리
-
Iterator(반복자) : 요소를 순서대로 검색하는 인터페이스(API)를 결정한다. 예제 프로그램에서는
Iterator<E>
인터페이스가 이 여할을 맡아서 다음 요소가 존재하는지 조사하는 hasNext 메소드, 다음 요소를 가져오는 next 메소드를 결정한다. -
Concretelterator(구체적인 반복자) : Iterator가 결정한 인터페이스(API)를 실제로 구현한다. 예제 프로그램에서는 BookShelfIterator 클래스가 이 역할을 맡았다. 이 역할은 검색에 필요한 정보를 가지고 있어야 한다. 예제 프로그램에서는 BookShelf 클래스의 인스턴스를 bookshelf 필드에서 기억하고, 검색 중인 책을 index 필드에서 기억한다.
-
Aggregate(집합체) : Iterator를 만들어 내는 인터페이스(API)를 결정한다. 이 인터페이스(API)는 ‘내가 가진 요소를 차례로 검색해 주는 사람’을 만들어 내는 메소드이다. 예제 프로그램에서는
Iterable<E>
인터페이스가 이 역할을 맡아서 iterator 메소드를 결정한다. -
ConcreateAggregate(구체적인 집합체) : Aggregate가 결정한 인터페이스(API)를 실제로 구현한다. 구체적인 Iterator 역할, 즉 ConcreateIterator의 인스턴스를 만들어 낸다. 예제 프로그램에서는 BookShelf 클래스가 이 역할을 맡아서 iterator 메소드를 구현한다.