나를 기록하다
article thumbnail
반응형

JAVA 언어로 배우는 디자인 패턴 입문(출처: yes24)

Iterator 패턴

무엇인가 많이 모여 있을 때 이를 순서대로 가리키며 전체를 검색하고 처리를 반복하는 것.

 

예제 프로그램

책장(BookShelf) 안에 책(Book)을 넣고, 책 이름을 차례대로 표시하는 프로그램

예제 프로그램의 클래스 다이어그램

이름 설명
Iterable<E> 집약체를 나타내는 인터페이스(java.lang 패키지)
예제 프로그램에서는 Iterable<Book>으로 사용
Iterator<E> 처리를 반복하는 반복자를 나타내는 인터페이스(java.util 패키지)
예제 프로그램에서는 Iterator<Book>으로 사용
Book 책 클래스
BookShelf 책장 클래스
BookeShelfIterator 책장을 검색하는 클래스
Main 동작 테스트용 클래스

 

Iterable<E> 인터페이스

처리를 반복할 대상을 나타내는 것, java.lnag 패키지에 선언되어 있다. 이 인터페이스를 구현하는 클래스는 배열처럼 '많이 모여 있는 것' 이른바 '집합체'가 된다. 여기서 E는 타입 파라미터라는 것으로, '모여 있는 것'을 나타내는 타입을 지정한다.

public interface Iterable<E> {
    public abstract Iterator<E> iterator();
}

 

Iterator<E> 인터페이스

하나하나의 요소 처리를 반복하기 위한 것으로 루프 변수와 같은 역할을 한다.

public interface Iterator<E> {
    public abstract boolean hasNext();
    public abstract E next();
}
  • hasNext(): 다음 요소가 존재하면 true, 마지막 요소까지 도달했다면 false 반환
  • next()
    • 반환값 형태가 파라미터 E → 집합체의 요소 1개 반환
    • 다음 next 메서드를 호출할 때 제대로 다음 요소를 반환할 수 있도록 내부 상태를 다음으로 진행시켜 놓는 역할

 

Book 클래스

책을 나타내는 클래스. 책 이름을 getName 메서드로 얻음. 책 이름은 생성자에서 인스턴스 초기화시 인수로 지정

public class Book {
    private String name;

    public Book(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

 

BookShelf 클래스

책장을 나타내느 클래스. 집합체로 다루기 위해 Iterable<Book> 인터페이스 구현

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 BookShelfIterator(this);
    }
}
  • books 필드는 Book 배열. Book 배열의 크기는 처음에 BookShelf 인스턴스를 만들 때 인수(maxSize)로 지정

 

BookShelfIterator 클래스

BookShelf 클래스의 검색을 실행하는 클래스

import java.util.Iterator;
import java.util.NoSuchElementException;

public class BookShelfIterator implements Iterator<Book> {
    private final BookShelf bookShelf;
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < bookShelf.getLength();
    }

    @Override
    public Book next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}

Iterator<Book> 인터페이스를 구현하고 있으므로 Iterator<Book>형으로 다룰 수 있다.

bookShelf 필드는 BookShelfIterator가 검색할 책장, index 필드는 현재 보고 있는 책을 가리키는 첨자

  • hasNext()
    • Iterator<Book> 인터페이스에서 선언된 메서드를 구현. 다음 책이 있는지 조사하여 있으면 true, 없으면 false 반환
    • 다음 책이 있는지 여부는 index가 책장에 꽂힌 책의 수(bookShelf.getLength())보다 작은지 비교해서 판정
  • next()
    • 현재 주목하는 책(Book의 인스턴스)을 반환하고 다시 다음으로 진행시키는 메서드.
    • 반환값으로 돌려 줄 책을 book 변수에 저장해 두고, Index를 다음으로 진행시킨 후(index++) book을 return

 

Main 클래스

책장을 만들고 책을 표시하는 클래스

import java.util.Iterator;

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();
        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());
        }
        System.out.println();
    }
}

 

명시적으로 Iterator를 사용하는 방법

검색하지 않은 책이 남아 있는 한 while문의 루프가 돌아간다. 그리고 루프 안에서 it.next()로 책을 가져와 표시한다.

확장 for문을 사용하는 방법

while문과 완전히 같은 동작을 한다.확장 for문을 사용하면 Iterator를 사용한 반복 처리를 간결하게 기술할 수 있다.

Java의 확장 for문의 배후에서는 Iterator 패턴이 사용된다고 볼 수 있다.

 

Iterator 패턴의 클래스 다이어그램

Iterator(반복자)

요소를 순서대로 검색하는 인터페이스(API)를 결정.

hasNext 메서드, next 메서드

ConcreteIterator(구체적인 반복자)

Iterator가 결정한 인터페이스(API)를 실제로 구현.

BookShelfIterator 클래스. 검색에 필요한 정보를 가지고 있어야 한다.

BookShelf 클래스의 인스턴스를 bookShelf 필드에서 기억하고, 검색 중인 책을 index 필드에서 기억

Aggregate(집합체)

Iterator를 만들어 내는 인터페이스(API)를 결정.

내가 가진 요소를 차례로 검색해주는 사람을 만들어 내는 메서드.

Iterable<E> 인터페이스가 이 역할을 맡아 iterator 메서드 결정

ConcreteAggregate(구체적인 집합체)

Aggregate가 결정한 인터페이스(API)를 실제로 구현

구체적인 Iterator 역할, 즉 ConcreteIterator의 인스턴스를 만들어냄.

BookShelf 클래스가 이 역할을 맡아서 iterator 메서드 구현

 

Iterator 패턴을 사용하는 이유

가장 큰 이유는 Iterator를 사용함으로써 구현과 분리하여 반복할 수 있기 때문이다.

while(it.hasNext()) {
    Book book = it.next();
    System.out.println(book.getName());
}

hasNext와 next라는 Iterator 메서드만을 사용하고 BookShelf 구현에 사용된 메서드는 호출되지 않는다.

즉, while 루프는 BookShelf 구현에 의존하지 않는다.

 

배열로 책을 관리하는 것에서 java.util.ArrayList를 사용하도록 프로그램을 변경했다고 가정하자. BookShelf를 어떻게 변경하든 BookShelf가 iterator 메서드를 가지고 있고 올바른 Iterator<Book>을 반환하면(hasNext 및 next 메서드가 바르게 구현된 클래스의 인스턴스를 반환하면), 위의 while 루프는 변경하지 않아도 동작한다.

 

디자인 패턴은 클래스의 재사용을 촉진한다. 클래스를 부품처럼 사용할 수 있게 만들어, 어떤 한 부품을 수정하더라도 다른 부품을 수정할 일이 적어진다는 것이다.

 

이렇게 생각한다면 예제에서 메서드 iterator의 반환값을 BookShelfIterator형 변수에 대입하지 않고 Iterator<Book>형 변수에 대입한 이유도 알 수 있다. BookShelfIterator의 메서드를 쓰지 않고, 끝까지 Iterator<Book>의 메서드로 프로그래밍하려 한 것이다.

 

추상 클래스와 인터페이스를 사용하여 프로그래밍하는 이유

구체적인 클래스만 사용하면 클래스 사이의 결합이 강해져 부품으로 재사용하기 어렵다. 결합을 약화하고 클래스를 부품으로 재사용하기 쉽게 하고자 추상 클래스나 인터페이스를 도입한다.

 

Aggregate와 Iterator의 대응

BookShelfIterator는 BookShelf가 어떻게 구현되어 있는지 알고 있었기 때문에 다음 책을 가져오는 메서드 getBookAt을 호출할 수 있다. BookShelf의 구현을 완전히 바꿔 getBookAt 메서드라는 인터페이스(API)도 변경했을 때는 BookShelfIterator를 수정해야 한다는 것이다. Iterable<E>와 Iterator<E>라는 두 개의 인터페이스가 짝을 이루듯이 BookShelf와 BookShelfIterator라는 두 개의 클래스도 짝을 이룬다.

 

복수의 Iterator

현재 어디까지 조사했는지 기억하는 구조를 Aggregate 역할 외부에 두는 것이 Iterator 패턴의 특징 중 하나이다. 이러한 특징에 따라 하나의 ConcreteAggregate 역할에 대해 여러 개의 ConcreteIterator 역할을 만들 수 있다.

 

 

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...