오늘은 디자인 패턴 중에 Iterator(반복자)에 대해 공부한 내용을 작성해 보려고 한다. 해당 포스팅은 "Java 언어로 배우는 디자인 패턴 입문" 이라는 책을 바탕으로 작성되었다. 참고로 이 책은 Gof 디자인 패턴을 읽기 전 입문서로 적합한 책이다.
Iterator(반복자) 패턴 정의
무엇인가 많이 모여 있는 것들을 순서대로 지정하면서 전체를 검색하는 처리를 실행하기 위한 패턴
사실 딱 읽고 와 닿지 않는 말이다.
이 말을 조금 쉽게 이해하기 위해 먼저 아래 for문을 살펴보자. Java 언어에서 배열 arr의 모든 요소를 표시하기 위해서는 아래처럼 코드를 작성할 수 있다.
for (int i = 0; i < arr.length < i++) {
System.out.println(arr[i]);
}
이제 코드를 위에 정의했던 내용에 적용시켜 보자.
"무엇인가 많이 모여 있는 것들" : arr 배열
"순서대로 지정하면서 전체를 검색" : i 변수
여기서 주목해봐야 하는 건 "i" 라는 변수이다. "i" 변수는 다음과 같은 기능을 한다. 1) 배열의 위치를 나타내 준다. 2) 값을 증가/감소 시켜 배열의 전체를 검색할 수 있도록 도와준다.
이 기능을 추상화하여 일반화 한 것을 Iteator(반복자) 패턴이라고 부른다.
책(Book)을 관리해 주는 서가(BookShelf)와 같은 기능이 필요하다.
(관리 기능)
1. 서가(BookShelf)에 책(Book)을 넣을 수 있다.
2. 서가(BookShelf) 내부에 있는 모든 책(Book)을 순서대로 검색 혹은 표시 할 수 있다.
(제약 조건)
1. for loop 같은 반복문을 통해 배열에 직접 접근하지 않는다.
2. Java에서 기존에 제공하는 Iterator를 사용하지 않는다.
이름 | 역할 |
---|---|
Aggreate | 집합체를 나타내는 인터페이스 |
Iterator | 하나씩 나열하면서 검색을 실행하는 인터페이스 |
Book | 책을 나타내는 클래스 |
BookShelf | 서가를 나타내는 클래스 |
BookShelfIterator | 서가를 검색하는 클래스 |
Main | 테스트 클래스 |
public interface Aggreate {
public abstract Iterator iterator();
}
Aggregate 인터페이스를 통해 Iterator가 주어진 역할을 할 수 있도록 만들어 준다. (API 역할)
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
실제 요소를 순서대로 접근할 수 있는 메서드를 가지고 있는 인터페이스이다. 해당 인터페이스는 2개의 메서드를 가지고 있으며, 각각의 역할은 다음 요소가 존재하는 지 확인하는 hasNext() 메서드와 다음 요소를 가져오는 next()로 구성되어 있다. 사실 next() 메서드는 다음 요소를 가져올 수 있을 뿐 아니라 다음 요소로 이동하는 역할도 함께 수행되고 있다. (for loop "i++" 역할)
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public Strig getName() {
return name;
}
}
Book 클래스는 책을 나타내는 클래스이다. 이 클래스의 역할은 getName() 메서드를 통해 책의 이름을 제공해 준다.
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
public BookShelf(int maxSize) {
this.books = new Book[mazSize];
}
public Book getBookAt(int index) {
return book[index];
}
public void appendBook(Book book) {
this.books[last] = book;
}
public int getLength() {
return last;
}
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
Aggregate 인터페이스를 상속받은 걸로 보아 Aggregate 인터페이스에서 정의한 Iterator 기능을 가지고 있는 클래스라는 것을 눈치 챌 수 있다. 하지만 next(), hasNext()의 구체적인 기능은 보이지 않는다.
단지 BookShelfIterator 클래스를 생성하고 있다. 계속 살펴보자.
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
}
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
}
return false;
}
public Object next() {
Book book = bookShelf.getBookAt(index);
++index;
return book;
}
}
드디어 Iterator의 구현부가 있는 클래스이다. hasNext() 메서드와 next() 메서드가 구현되어 있다. 위에서 잠깐 언급했듯이 next() 메서드에는 다음 요소도 이동도 포함이 되어 있다는 사실을 알 수 있다.
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 it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}
}
}
위에서 살짝 언급한 내용이다.
Main 클래스에 이상한 부분이 하나 있다. 바로 Iterator it=bookShelf.iterator();
여기다. BookShelfIterator 클래스의 모습은 어디에도 안 보인다. 이 클래스에 내 모든 핵심이 있는데...왜 안 보일까? 다시 BookShelf 클래스로 돌아가 보자. 맨 밑에 new BookShelfIterator();
BookShelf 클래스에서 이미 만들어 놓았다. 이미 만들어진 객체를 그냥 사용하기만 하면 된다. 심지어 Main 클래스는 BookShelfIterator의 존재를 전혀 알지 못해도 Iterator를 사용하는 데 아무 문제가 없다.
객체지향에서 많이 이야기하는 재 사용성에 대한 고민이 들어간 패턴이라는 생각이 든다. 사실 개발하기도 바쁜데, 재 사용성을 고려해서 코딩하기는 생각보다 쉽지는 않을 뿐더라 고민도 많이 해야 하는 부분이다. 어떻게 보면 재 사용성을 고려한다는 건 변화가 많이 일어날 가능성이 있는 부분과 그나마 변화가 작게 일어날 곳을 구분하는 것에서 출발한다고 생각한다. 재 사용을 할 수 있는 곳은 당연하게 변화가 적게 일어나는 곳에서 가능할 것이다. 위에 예제를 다시 보면 서가 관리에서 변화가 집중될 수 있는 BookShelf 클래스와 반복문을 분리함으로 반복문 재 사용 효과를 노려 본 패턴이라 할 수 있다.