설계 패턴 : 이터레이터 패턴

박철민·2023년 4월 6일
0

CS 지식

목록 보기
7/8

이터레이터 패턴

이터레이터 패턴이란?

이터레이터 패턴은 이터레이터를 사용하여 컬랙션의 요소들에 접근하는 디자인 패턴입니다.
이를 통해 순회할 수 있는 각기 다른 자료형의 구조와는 상관없이 이터레이터라는 하나의 인터페이스로 순회가 가능해서 코드의 효율화, 숫자형 index가 아닌 String등 다양한 인덱스를 기반으로 순회를 쉽게할 수 있습니다.

위에 명시된 하나의 인터페이스에 주목하면 됩니다.

여러 다양한 형태의 자료형들을 하나의 인터페이스로 묶어보는 걸로 이터레이터로 할 수 있습니다.

이터레이터 패턴 사용법

일반 적인 반복문에서는 for문을 사용합니다.

for(int i=0; i<N; i++){
	System.out.println(arr[i]);
}

다음과 같이 변수 i를 사용하여 배열의 각각의 요소에 차례대로 접근하기 위해 사용됩니다.

이렇게 사용되는 변수 i의 역할을 추상화 한 것이 Iterator 패턴입니다.

예제(https://zion830.tistory.com/33)

책꽂이 안에 책을 꽂고, 다시 책을 하나씩 확인하는 예제를 작성하겠습니다.

1) Book : 한 권의 책에 대한 정보를 가지고 있는 클래스

class Book {
	private String name;
	
	public Book(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}

2) Aggregate : 집합체를 의미하는 인터페이스
Aggreagate는 Iterator 역할을 만들어내는 인터페이스를 결정한다.


interface Aggregate {
	public abstract Iterator createIterator();
}

3) BookShelf : 책을 보관하는 책꽂이 역할을 하는 클래스
BookShelf는 이러한 Aggregate의 구현체이다.
실제로 책꽂이 안을 돌아다닐 Iterator을 생성하고 책꽂이를 관리하는 역할을 한다.

class BookShelf implements Aggregate {
	private Book[] books;
	private int last = 0;
	
	public BookShelf(int size) {
		books = new Book[size];
	}
	
	public Book getBook(int index) {
		return books[index];
	}
	
	public int getLength() {
		return last;
	}
	
	public void appendBook(Book book) {
		if(last < books.length) {
			this.books[last] = book;
			last++;
		} else {
			System.out.println("책꽂이가 꽉 찼습니다.");
		}
	}
	
	@Override
	public Iterator createIterator() {
		return new BookShelfIterator(this);
	}
}

4) BookShelfIterator : BookShelf 클래스에서 검색을 수행하는 클래스
BookShelfIterator를 Iterator로 다루기 위해 Iterator 인터페이스를 상속받았습니다. Iterator 인터페이스는 Java에 이미 내장되어 있기 때문에 따로 구현하지 않습니다.

Iterator 인터페이스에 대한 공식 문서 https://docs.oracle.com/javase/8/docs/api/

class BookShelfIterator implements Iterator<Book> {
	private BookShelf bookShelf; // 검색을 수행할 책꽂이
	private int index = 0; // 현재 처리할 책의 위치
	
	public BookShelfIterator(BookShelf bookShelf) {
		this.bookShelf = bookShelf;
	}
	
	@Override
	public boolean hasNext() {
		return index < bookShelf.getLength();
	}

	@Override
	public Book next() {
		Book book = bookShelf.getBook(index++);
		return book;
	}
}

next()는 다음번 요소를 반환하고, hasNext()는 검색을 계속 수행해도 될지의 여부를 판별합니다.

이제 실제로 책꽂이에 책을 꽂고 책을 하나씩 검색해 이름을 출력합니다.

public class IteratorBookShelf {
	public static void main(String[] args) {
		BookShelf bookShelf = new BookShelf(10);
		
		Book book1 = new Book("Bilbe");
		Book book2 = new Book("Cinderella");
		Book book3 = new Book("Daddy-Long-Legs");

		bookShelf.appendBook(book1);
		bookShelf.appendBook(book2);
		bookShelf.appendBook(book3);
		
		System.out.println("현재 꽂혀있는 책 : " + bookShelf.getLength() + "권");
		
		Iterator it = bookShelf.createIterator();
		while(it.hasNext()) {
			Book book = (Book) it.next();
			System.out.println(book.getName());
		}
	}
}

다음과 같이 코드를 짤 수 있습니다.

총 코드

import java.util.Iterator;

// 한 권의 책에 대한 정보를 가지고 있는 클래스
class Book {
	private String name;
	
	public Book(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}

// 집합체를 의미하는 인터페이스
// Aggreagate는 Iterator 역할을 만들어내는 인터페이스를 결정한다.
interface Aggregate {
	public abstract Iterator createIterator();
}

class BookShelf implements Aggregate {
	private Book[] books;
	private int last = 0;
	
	public BookShelf(int size) {
		books = new Book[size];
	}
	
	public Book getBook(int index) {
		return books[index];
	}
	
	public int getLength() {
		return last;
	}
	
	public void appendBook(Book book) {
		if(last < books.length) {
			this.books[last] = book;
			last++;
		} else {
			System.out.println("책꽂이가 꽉 찼습니다.");
		}
	}
	
	@Override
	public Iterator createIterator() {
		return new BookShelfIterator(this);
	}
}

class BookShelfIterator implements Iterator<Book> {
	private BookShelf bookShelf; // 검색을 수행할 책꽂이
	private int index = 0; // 현재 처리할 책의 위치
	
	public BookShelfIterator(BookShelf bookShelf) {
		this.bookShelf = bookShelf;
	}
	
	@Override
	public boolean hasNext() {
		return index < bookShelf.getLength();
	}

	@Override
	public Book next() {
		Book book = bookShelf.getBook(index++);
		return book;
	}
}

public class IteratorBookShelf {
	public static void main(String[] args) {
		BookShelf bookShelf = new BookShelf(10);
		
		Book book1 = new Book("Bilbe");
		Book book2 = new Book("Cinderella");
		Book book3 = new Book("Daddy-Long-Legs");

		bookShelf.appendBook(book1);
		bookShelf.appendBook(book2);
		bookShelf.appendBook(book3);
		
		System.out.println("현재 꽂혀있는 책 : " + bookShelf.getLength() + "권");
		
		Iterator it = bookShelf.createIterator();
		while(it.hasNext()) {
			Book book = (Book) it.next();
			System.out.println(book.getName());
		}
	}
}

왜 이터레이터 패턴을 사용할까요?

위에 for문으로 동일한 표현을 할 수 있는데 왜 이터레이터 패턴을 사용해야 할까요? 위에 보면 이것을 구현하기 위해 많은 코드를 썼는데요.
Iterator 패턴을 사용하는 가장 큰 이유는 하나씩 꺼내서 처리하는 과정을 구현과 분리할 수 있기 때문입니다.

Iterator 패턴을 사용하였을 때의 코드입니다.

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

for문을 사용했을 때입니다.

for(int i = 0; i < bookShelf.getLength(); i++) {
	System.out.println(bookShelf.getBook(i).getName());
}

결과값은 같아보이지만 위에 적은 하나씩 꺼내서 처리하는 과정을 구현과 분리에 집중해보시기 발랍니다.
for문을 사용할 떄와 달리 Iterator 패턴을 사용할 때는 어디까지나 Iterator의 메서드를 사용할 뿐 BookShelf의 구현에서 사용되고 있는 메서드는 호출되지 않고 있습니다.

즉, Iterator 방식은 BookShelf의 구현에 의존하지 않는다는 점입니다.

예를 들어 bookShelf의 메서드 명인 getBook의 이름이 바뀌어도 Iterator는 상관없이 it.next()를 사용하여 다음을 받아오게 됩니다.
즉, BookShelf에 무언가 수정사항이 생기더라도 BookShelft가 올바른 Iterator를 반환하기만 한다면 while문이 사용되는 Main은 수정할 필요가 없는 것입니다.
하나의 클래스를 수정하더라도 다른 클래스의 큰 영향없이 작은 수정만으로도 끝낼 수 있다는 것입니다.

참고자료

profile
멘땅에 헤딩하는 사람

0개의 댓글