무언가 많이 모여있는 것들을 순서대로 지정하면서 전체를 검색하는 처리를 위한 것.
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
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 BookShelfIterator(this);
}
}
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;
}
}
Iterator의 역할 : 요소를 어떻게 검색해 나갈지에 대해 어떻게 명세할 것인지에 대한 약속, BookShelfIterator에서 이용한 인터페이스인 Iterator이다.
ConcreteIterator(구현체)의 역할 : Iterator 인터페이스에 따라 요소를 구체적으로 어떻게 읽어 들일지에 대한 처리. 예제에서는 BookShelfIterator이다.
Aggregate(집합체)의 역할 : Iterator를 만들어내는 부분에 대한 약속을 결정. '내가 가지고 있는 요소를 탐색해주는 사람'을 만들어내는 메소드를 포함한다. 예시에서는 Iterable이다.
ConcreteAggregate(집합 구현체)의 역할 : 실제로 요소를 검색해나가는 도구를 만들어내는 역할을 한다. 예제에서는 BookShelf가 이 역할을 한다.
이유는 아래와 같다. Main에서 아래와 같은 함수를 사용한다고 생각해보자. 아래의 코드는 bookShelf 내부에서 어떤 자료형으로 Book이 저장 되어있는지 알지 못한다. 이것이 중요한 포인트이다. 예를 들어 변경이 bookShelf 내부에서 Book을 관리하는 자료형에 변경이 일어났다고 생각해보자.
이 자료형에 변경이 일어나도, Iterator 인터페이스에 맞게 구현한다면, Main의 코드를 변경할 필요는 없을 것이다. 즉 books에 대한 책임을 결국 bookShelf에서만 가지게 된다. 디자인패턴은 클래스의 재이용화를 촉진하게 된다.
book을 읽어들이고 관리하는 책임을 Main이 아닌 bookShelfIterator와 bookShelf로 옮겨왔다고 보면 될 것 같다.
Iterator<Book> it = bookShelf.iterator();
while (it.hasNext()) {
Book book = it.next();
System.out.println(book.getName());
}
우리가 상속하여 더 많은 Iterator를 만들 수도 있다. 또한 다양한 Iterator가 존재한다.
ListIterator : arrayList, linkedList 등과 같은 List 컬렉션에서 사용되는 클래스. Iterator와 다르게 양방향을 지원, 데이터를 추가하거나 교체하는것도 가능.
Enumeration : Legacy Collection(Vector, Hashtable)의 데이터를 가져오는데 사용되는 클래스. 위의 Vector와 Hashtable에서만 사용이 가능하여 범용적인 클래스는 아니다. 순방향으로 읽어들이기만 가능, 추가하거나 제거하는 작업은 불가능
출저 : Java 언어로 배우는 디자인 패턴 입문