자바 디자인 패턴

1. Iterator - 처리를 반복한다

조요피 2023. 10. 4. 10:32
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

for문의 i++에서 i를 하나씩 증가시킬때마다 다음의 요소로 진행된다.

이때, i의 기능을 추상화하여 일반화한 것을 디자인 패턴에서는 Iterator 패턴이라고 한다.

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

iterate라는 영단어는 무언가를 '반복하다'라는 뜻.

그래서 '반복자'라고도 한다.

Iterable 인터페이스

처리를 반복할 대상을 나타내는 것.

이 인터페이스를 구현하는 클래스는 배열처럼 '뭔자 많이 모여 있는 것' 즉, '집합체'가 된다.

Iterable은 '반복 가능'이라는 뜻.

Iterator 인터페이스

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

BookShelf 클래스

implements Iterable 부분이 Iterable 인터페이스를 구현하고 있음을 나타냄.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class BookShelf implements Iterable<Book> {
    private List<Book> books;

    public BookShelf(int initialsize) {
        this.books = new ArrayList<>(initialsize);
    }

    public Book getBookAt(int index) {
        return books.get(index);
    }

    public void appendBook(Book book) {
        books.add(book);
    }

    public int getLength() {
        return books.size();
    }

    @Override
    public Iterator<Book> iterator() {
        return new BookShelfIterator(this);
    }
}

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;
    }
}

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"));
        bookShelf.appendBook(new Book("East of Eden"));
        bookShelf.appendBook(new Book("Frankenstein"));
        bookShelf.appendBook(new Book("Gulliver's Travels"));
        bookShelf.appendBook(new Book("Hamlet"));

        // 명시적으로 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 패턴을 사용하는 이유

반복문을 사용하면 되는데 왜 굳이 Iterator를 만들어야 할까?

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

또한, iterator를 사용하면 모든 컬렉션에서 요소를 순차적으로 접근하는 일관된 방법을 제공한다.

bookShelf의 구현이 달라지더라도 iterator를 반환하기만 한다면 반복문(while)의 변경은 하지 않아도 된다.

Iterator<Book> it=bookShelf.iterator();
        while(it.hasNext()){
        Book book=it.next();
        System.out.println(book.getName());
        }

클래스로만 프로그래밍 하는 것이 아니라 추상 클래스나 인터페이스를 사용하여 프로그래밍 한다.

Iterable과 Iterator의 차이

Iterable은 컬렉션을 for-each 루프와 같은 향상된 for 루프를 사용하여 순회할 수 있게 해주는 인터페이스이며, Iterator는 컬렉션을 명시적으로 순회하고 조작하는 인터페이스입니다. Iterator
컬렉션의 요소를 보다 세밀하게 제어하고 수정할 때 유용합니다.

Iterable을 구현하면 Iterator도 구현했다고 볼 수 있겠네?

예, Iterable을 구현한 클래스는 일반적으로 내부적으로 Iterator를 구현하거나 Iterator 객체를 반환하는 iterator() 메서드를 제공합니다. 따라서 Iterable을 구현하면
컬렉션을 순회하는 데 필요한 Iterator도 함께 구현했다고 볼 수 있습니다.

Iterable을 구현하면 주로 iterator() 메서드를 구현하여 해당 컬렉션의 요소를 순회할 수 있는 Iterator 객체를 반환합니다. 이 Iterator 객체는 hasNext()
next() 메서드를 사용하여 요소에 접근하고 순회합니다. 이렇게 함으로써 사용자는 for-each 루프와 같은 구문을 사용하여 컬렉션을 순회할 수 있습니다.

예를 들어, 다음은 Iterable을 구현한 클래스와 그 클래스의 iterator() 메서드의 예제입니다:

import java.util.Iterator;

public class MyIterable implements Iterable<String> {
    private String[] data;

    public MyIterable(String[] data) {
        this.data = data;
    }

    @Override
    public Iterator<String> iterator() {
        return new MyIterator(data);
    }
}

class MyIterator implements Iterator<String> {
    private String[] data;
    private int index = 0;

    public MyIterator(String[] data) {
        this.data = data;
    }

    @Override
    public boolean hasNext() {
        return index < data.length;
    }

    @Override
    public String next() {
        return data[index++];
    }
}

이 예제에서 MyIterable 클래스는 Iterable을 구현하고, iterator() 메서드에서 MyIterator 객체를 생성하여 반환합니다. MyIterator 클래스는 Iterator
구현하고 컬렉션을 순회합니다.

따라서 MyIterable을 사용하면 내부적으로 Iterator도 구현되어 있어서 컬렉션을 순회할 수 있습니다.