Iterator 패턴
- 많이 모여있는 데이터를 순서대로 가르키며 전체를 검색하고 처리를 반복하는 것 (반복자라고도 한다.)
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
변수 i의 기능을 추상화하여 일반화한 것을 디자인 패턴에서는 Iterator 패턴 이라고 한다.
책에 나와있는 예제를 통해서 알아보았다.
이름 | 설명 |
Iterable<E> | 집합체를 나타내는 인터페이스(java.lang패키지) 여기서 Iterable<Book> 사용 |
Iterator<E> | 처리를 반복하는 반복자를 나타내는 인터페이스(java.util) Iterator<Book>으로 사용 |
Book | 책을 나타내는 클래스 |
BookShelf | 책장을 나타내는 클래스 |
BookShelfIterator | 책장을 검색하는 클래스 |
Main | 동작 테스트용 클래스 |
하나씩 예제를 보고 알아보자
Iterable<E>인터페이스
이 인터페이스를 구현하는 클래스는 ‘배열처럼 데이터가 많이 모여있는’ 집합체 이다. (ArrayList로 구현해도 상관없음)
제네릭 안에들어가는 타입은 어떻게 보면 ‘여기에 모여 있는 것’을 나타내는 타입을 지정하는 것이다.
public interface Iterable<T> {
public abstract Iterator<T> iterator();
}
iterator() 메서드는 집합체에 대응하는 Iterator<E>를 만들기 위한 것이다.
- 집합체에 포함된 요소를 하나하나 처리해 나가고 싶을 때는 이 메서드를 사용해 Iterator<E> 인터페이스를 구현한 클래스의 인스턴스를 하나 만든다.
Iterator<E> 인터페이스
Iterator<E> 인터페이스는 하나하나의 요소처리를 반복하기 위한 것으로 루프 변수와 같은 역활을 한다.
public interface Iterator<E> {
public abstract boolean hasNext();
public abstract E next();
}
‘다음 요소’가 존재하는지 알아보는 hasNext메서드, ‘다음 요소’를 가져오는 next 메서드
- hasNext메서드는 마지막 요소까지 이미 도달하면 false를 반환하기 때문에 루프 종료 조건으로 사용한다.
- next메서드는 E(Element, 요소) 써있는 것 처럼 집합체의 요소를 1개 반환한다.
- next메서드를 호출할 때 제대로 다음 요소를 반환할 수 있도록 내부 상태를 다음으로 진행시켜 놓는 역활도 숨어 있다.
Book 클래스
책을 나타내는 클래스 (요소)
public class Book {
private String name;
public Book (String name){
this.name = name;
}
public String getName(){
return name;
}
}
- 책 이름을 getName 메서드로 얻는 것 뿐이다. (책 이름은 생성자에서 인스턴스를 초기화할 때 인수로 지정)
BookShelf클래스
책장을 나타내는 클래스로, 집합체로 다루기 위해 Iterable<Book> 인터페이스를 구현하고 있다.
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);
}
}
books 필드를 private로 지정한 이유는 이 클래스 밖에서 직접 접근하는 것을 방지하기 위해서 이다.
Iterable 인터페이스에서 선언된 iterator()를 오버라이드 했다.
- BookShelf 클래스에 대응하는 Iterator로서, BookShelfIterator 클래스의 인스턴스를 생성하여 반환한다.
- 책장에 꽂혀 있는 책을 반복해서 처리하고 싶을 때 iterator 메서드를 호출한다.
BookShelfIterator클래스
BookShelf 클래스의 검색을 실행하는 BookShelfIterator 클래스
- BookShelfIterator는 Iterator<Book> 인터페이스를 구현하고 있기 때문에 Iterator<Book>형으로 다룰 수 있다.
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;
}
}
- bookshelf 필드는 BookShelfIterator가 검색할 책장이고, index 필드는 현재 보고 있는 책을 가리키는 첨자이다.
- 생성자에서는 전달된 BookShelf 인스턴스를 bookshelf필드에 저장하고 index를 0으로 지정한다.
- hasNext 메서드는 Iterator<Book>인터페이스에서 선언된 메서드를 오버라이드 해서 구현했다.
- 다음 책이 있으면 true 없으면 false
- index가 bookShelf.getLength()(책장에 꽂힌 책의 값) 보다 작은지 비교한다.
- next메서드는 현재 보고있는 책(Book 인스턴스)를 반환한다. 그 후 index++ 해서 다음책으로 넘어간다.
- 이 메서드 역시 Iterator<Book>인터페이스에서 선언된 메서드를 오버라이드 해서 구현했다.
- Book book = bookShelf.getBookAt(index); 반환값으로 돌려 줄 책을 book변수에 저장한다.
- 그 후, index를 다음으로 진행시킨 후 저장했던 book을 return한다.
- index++ → 다음 책으로 넘어가는 것이 for문의 변수 i++에 해당하는 처리이다.(루프 변수를 ‘다음’으로 진행)
Main 클래스
동작 테스트용 클래스
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4); // 책이 4개 들어가게 생성자
bookShelf.appendBook(new Book("java의 정석"));
bookShelf.appendBook(new Book("디자인 패턴"));
bookShelf.appendBook(new Book("DDD"));
bookShelf.appendBook(new Book("TDD")); // 책 4권 저장
//명시적으로 iterator를 사용하는 방법
Iterator<Book> it = bookShelf.iterator();
while (it.hasNext()){
Book book = it.next();
System.out.println(book.getName());
}
System.out.println();
//확장 for문을 사용하는 방법
for (Book book : bookShelf){
System.out.println(book.getName());
}
}
}
책이 4권들어가는 책장을 만든 후 책을 4권 만들어서 넣어준다. 그 다음 bookShelf에 든 4권의 책을 2가지 방법으로 책 이름을 저장한 차례로 표시한다.
while문은 자주 사용하니 for문을 보자
//확장 for문을 사용하는 방법
for (Book book : bookShelf){
System.out.println(book.getName());
}
Iterator를 사용하지 않은건가 뭐지 할 수 있겠다.
확장 for문은 Iterable 인터페이스를 구현한 클래스의 인스턴스에 대해 내부적으로 Iterator를 사용하여 처리한다.
- java의 확장 for문 배후에서는 Iterator 패턴이 사용된다고 볼 수 있다.
for (int i : arr) {} // 자주 봤을 것이다.
추가적으로 Java의 배열은 Iterable인터페이스를 구현하지 않았지만, 확장 for문을 사용하여 요소에 대한 반복처리를 기술할 수 있다.
예제에서 나오는 Iterator 패턴의 등장인물
Iterator(반복자) 역
요소를 순서대로 검색하는 인터페이스(API)를 결정한다.
Iterator<E> 인터페이스가 이 역활을 맡아 다음 요소가 존재하는지 조사하는 hasNext 메서드, 다음 요소를 가져오는 next 메서드를 결정한다.
Concretelterator(구체적인 반복자) 역
Iterator가 결정한 인터페이스(API)를 실제로 구현합니다.
BookShelfIterator 클래스가 이 역활을 맡았다.
- 검색에 필요한 정보를 가지고 있어야 한다.
BookShelf 클래스의 인스턴스를 bookShelf 필드에서 기억하고, 검색 중인 책을 index필드에서 기억한다.
Aggregate(집합체) 역
Iterator를 만들어 내는 인터페이스(API)를 결정한다.
- 내가 가진 요소를 차레로 검색해 주는 사람을 만들어 내는 메소드이다.
Iterable<E>인터페이스가 이 역활을 맡아 iterator 메서드를 결정한다.
ConcreteAggregate(구체적인 집합체) 역
Aggregate가 결정한 인터페이스(API)를 실제로 구현합니다. → 컬렉션 프레임워크들
구체적인 Iterator 역활로 ConcreteIterator의 인스턴스를 만들어 낸다.
BookShelf 클래스가 이역활을 맡아서 iterator 메서드를 구현했다.
Iterator 패턴의 클래스 다이어그램 (그림 및 내용은 카테고리명 책을 통해서 공부했다.)
정리
어떻게 구현하든 Iterator를 사용할 수 있다.
배열이면 for문으로 돌리면 되는데 집합체 외부에 Iterator를 만들어야 하는 이유?
Iterator를 사용함으로써 구현과 분리하여 반복할 수 있기 때문이다.
while (it.hasNext()){
Book book = it.next();
System.out.println(book.getName());
}
여기서 사용된 것은 hasNext(), next() 메서드 뿐이다. BookShelf 구현에 사용된 메서드는 호출되지 않았다.
- while 루프는 BookShelf 구현에 의존하지 않는다.
BookShelf를 구현한 사람이 배열로 책을 관리하던 것을 그만두고 java.util.ArrayList를 사용하도록 프로그램을 변경해도 BookShelf를 어떻게 변경하든 BookShelf가 iterator 메서드를 가지고 있고 올바른 Iterator<Book>을 반환하면 (hasNext 및 next 메서드가 바르게 구현된 클래스의 인스턴스를 반환하면) 위의 while 루프는 변경하지 않아도 된다.
디자인 패턴은 클래스 재사용을 촉진한다
- 클래스를 부품처럼 사용할 수 있게 만들어, 어떤 한 부품을 수정하더라도 다른 부품을 수정할 일이 적어진다.
- iterator의 반환값을 BookShelfIterator형 변수에 대입하지 않고 Iterator<Book>형 변수에 대입한 이유
- BookShelfIterator의 메서드를 쓰지않고 Iterator<Book>의 메서드로 프로그래밍 하려는 것
추상 클래스와 인터페이스는 사용하기 어렵다.
추상 클래스와 인터페이스 사용법을 잘 모르는 사람은 Iterable<E>나 Iterator<E>와 같은 인터페이스를 사용하지 않고, 구체적인 클래스만 사용해서 프로그래밍 한다. -> .. 내이야기 노력은 해야지...!
구체적인 클래스만 사용은 클래스 사이의 결합이 강해져 부품으로 재사용하기 어려워진다.
- 결합을 약화하고 클래스를 부품으로 재사용하기 쉽게 하고자 추상 클래스나 인터페이스를 도입한다.
💡 추상 클래스나 인터페이스를 사용하여 프로그래밍한다는 사고방식을 가지자!!
Aggregate와 Iterator의 대응
BookShelf 클래스에 대응하는 ConcreteIterator 열활로 BookShelfIterator 클래스를 정의했었다.
BookShelfIterator는 BookShelf가 어떻게 구현되어 있는지 알고 있었기 때문에 getBookAt을 호출할 수 있었다.
- BookShelf의 구현을 완전히 바꿔, getBookAt 메서드라는 인터페이스(API)도 변경했을 때는 BookShelfIterator를 수정해야 된다는 것이다.
- Iterable<E>와 Iterator<E>라는 두 개의 인터페이스가 짝을 이루듯이 BookShelf와 BookShelfIterator라는 두개의 클래스도 짝을 이룬다.
‘next’는 혼동하기 쉽다.
이 메서드의 반환값은 현재 처리하는 요소일까 다음 요소일까..?
return CurrentElementAndAdvanceToNextPosition
next메서드는 ‘현재 요소를 반환하고 다음 위치로 진행’ 으로 생각해야 한다.
‘hasNext’도 혼동하기 쉽다.
hasNext 메서드는 마지막 요소를 얻기 전에는 true를 반환하지만 마지막 요소를 얻은 후에는 false를 반환한다.
- 주의해서 작성하지 않으면 마지막 요소 하나를 반환하지 못할 위험이 있다.
- hasNext는 다음에 next 메서드를 호출해도 괜찮은지 알아보는 메서드 라고 생각하자.
복수의 Iterator
Iterator 패턴의 특징 중 하나는: ‘현재 어디까지 조사했는지 기억하는 구조를 Aggregate 역활 외부에 두는 것’
- 특징에 따라 하나의 ConcreteAggregate 역활에 대해 여러 개의 ConcreteIterator 역활을 만들 수 있다.
deleteIterator는 필요없다 - java
Java에서는 가비지 컬렉션에 의해서 사용되지 않는 인스턴스는 자동으로 삭제된다.
- iterator에 대응하는 deleteIterator 메서드는 불필요하다.
관련패턴
Visitor 패턴
composite패턴
FactoryMethod 패턴
'책 > Java 언어로 배우는 디자인 패턴 입문' 카테고리의 다른 글
[디자인 패턴] Prototype패턴 (0) | 2023.03.12 |
---|---|
[디자인 패턴] Singleton패턴 (0) | 2023.03.11 |
[디자인 패턴] Factory Method패턴 (0) | 2023.03.11 |
[디자인 패턴] Template Method패턴 (0) | 2023.03.11 |
[디자인 패턴] Adapter패턴 (0) | 2023.03.11 |