본문 바로가기
Design Pattern/행동 패턴(Behavioral patterns)

Iterator Pattern, 반복자 패턴

by codeyaki 2024. 2. 28.
반응형

반복자 패턴이란?

컬렉션에는 리스트, 트리, 그래프 테이블 등등이 있다

리스트나 배열 같은 경우 단순히 반복문을 통해서 순차적으로 접근할 수 있지만 트리나 그래프 같은 경우 순회는 방법을 잡기 매우 애매해진다.

예를 들어 트리의 경우에도 깊이우선탐색, 너비우선탐색이 있으니 말이다.

이런 경우 이터레이터 패턴을 통해서 원소에 접근하는 방법을 추상화하여 이터레이터 패턴을 적용시킬 수 만 있다면 사용자가 원하는 방법으로 편하게 접근할 수 있게 된다.

접근하는 방법을 추상화하여 분리하였기 때문에 컬렉션의 내부 구조를 드러내지 않고 순차적으로 요소를 접근할 수 있게 된다.

패턴을 왜 사용해야 하는가?

  • 컬렉션의 내부 구조를 숨길 수 있다. 즉, 내부 구조 및 순회방식을 알지 않아도 사용할 수 있다.
  • 컬렉션의 구체적인 구현에 관계없이 동일한 방식으로 순회할 수 있다.
  • 다양한 알고리즘을 컬렉션에 쉽게 적용할 수 있다

패턴을 언제 사용해야 하는가?

  • 컬렉션의 요소를 순차적으로 처리해야 할때
  • 여러 컬렉션에 객체 접근 방식을 통일하고자 할 때
  • 컬렉션에 접근하는 방법을 다양하게 지원하고 싶을 때
  • 클라이언트로부터 컬렉션의 구조를 숨기고 싶을 때
  • 컬렉션의 종류를 변경할 가능성이 있을 때

문제점

  • 반복자 클래스를 따로 만들어야 하기 때문에 복잡성이 증가할 수 있다.

구현 방법

간단하게 책을 관리하는 코드를 작성해 보았다.

package com.example.designpattern.behavioral.iterator.book;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDate;

@Data
@AllArgsConstructor
public class Book {
    private String name;
    private String author;
    private LocalDate date;
}

 

package com.example.designpattern.behavioral.iterator;

import com.example.designpattern.behavioral.iterator.book.Book;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class NoPatternClient {
    public static void main(String[] args) {
        List<Book> bookshelf = new ArrayList<>();
        bookshelf.add(new Book("객체지향의 사실과 오해", "조영호", LocalDate.of(2015, 6, 17)));
        bookshelf.add(new Book("자바의 신", "이상민", LocalDate.of(2023, 10, 16)));
        bookshelf.add(new Book("자바의 정석", "남궁성", LocalDate.of(2016, 1, 27)));
        bookshelf.add(new Book("나미야 잡화점의 기적", "히가시노 게이고", LocalDate.of(2012, 12, 19)));

        bookshelf.sort(Comparator.comparing(Book::getName));
        for (Book book : bookshelf) {
            System.out.println(book);
        }
        System.out.println("============================================");

        bookshelf.sort(Comparator.comparing(Book::getDate));
        for (Book book : bookshelf) {
            System.out.println(book);
        }


    }
}

디자인 패턴을 적용하지 않으면 이처럼 클라이언트 쪽에서 직접 정렬하고 내부의 구현상태를 알고 있어야 한다. (name과 date가 있다는 것을 인지해야 함)

반복자 패턴을 적용하면 다음과 같이 수정할 수 있다.

먼저 반복자 인터페이스를 만들어 준다.

package com.example.designpattern.behavioral.iterator.iterator;

public interface Iterator<T> {
    boolean hasNext();
    T next();
}

 

package com.example.designpattern.behavioral.iterator.iterator;

import com.example.designpattern.behavioral.iterator.book.Book;

import java.util.Comparator;
import java.util.List;

public class NameIterator implements Iterator<Book> {
    private List<Book> list;
    private int index;

    public NameIterator(List<Book> list) {
        this.list = list;
        this.list.sort(Comparator.comparing(Book::getName));
    }

    @Override
    public boolean hasNext() {
        return index < list.size();
    }

    @Override
    public Book next() {
        return list.get(index++);
    }
}
package com.example.designpattern.behavioral.iterator.iterator;

import com.example.designpattern.behavioral.iterator.book.Book;

import java.util.Comparator;
import java.util.List;

public class DateIterator implements Iterator<Book> {
    private List<Book> list;
    private int index;

    public DateIterator(List<Book> list) {
        this.list = list;
        this.list.sort(Comparator.comparing(Book::getDate));
    }

    @Override
    public boolean hasNext() {
        return index < list.size();
    }

    @Override
    public Book next() {
        return list.get(index++);
    }
}

 

예시가 단순 List이기 때문에 클라이언트에서 직접 구현하는 게 더 간단해 보일 수 있는 것 같다.

하지만 컬렉션이 List가 아니라 복잡한 구조로 이뤄져 있다면 반복자패턴은 더욱 빛을 볼 것 같다.

 

package com.example.designpattern.behavioral.iterator;

import com.example.designpattern.behavioral.iterator.book.Book;
import com.example.designpattern.behavioral.iterator.book.Bookshelf;
import com.example.designpattern.behavioral.iterator.iterator.Iterator;

import java.time.LocalDate;

public class IteratorClient {
    public static void main(String[] args) {
        Bookshelf bookshelf = new Bookshelf();
        bookshelf.addBook(new Book("객체지향의 사실과 오해", "조영호", LocalDate.of(2015, 6, 17)));
        bookshelf.addBook(new Book("자바의 신", "이상민", LocalDate.of(2023, 10, 16)));
        bookshelf.addBook(new Book("자바의 정석", "남궁성", LocalDate.of(2016, 1, 27)));
        bookshelf.addBook(new Book("나미야 잡화점의 기적", "히가시노 게이고", LocalDate.of(2012, 12, 19)));

        // 이름 순 이터레이터
        Iterator<Book> nameIterator = bookshelf.getNameIterator();
        while (nameIterator.hasNext()) {
            System.out.println(nameIterator.next());
        }

        System.out.println("============================================");
        
        // 날짜 순 이터레이터
        Iterator<Book> dateIterator = bookshelf.getDateIterator();
        while (dateIterator.hasNext()) {
            System.out.println(dateIterator.next());
        }


    }
}

클라이언트 부분은 내부 구현상황을 몰라도 이처럼 손쉽게 요소들에 접근할 수 있게 된다.

 

실행 결과는 다음과 같다.

Book(name=객체지향의 사실과 오해, author=조영호, date=2015-06-17)
Book(name=나미야 잡화점의 기적, author=히가시노 게이고, date=2012-12-19)
Book(name=자바의 신, author=이상민, date=2023-10-16)
Book(name=자바의 정석, author=남궁성, date=2016-01-27)
============================================
Book(name=나미야 잡화점의 기적, author=히가시노 게이고, date=2012-12-19)
Book(name=객체지향의 사실과 오해, author=조영호, date=2015-06-17)
Book(name=자바의 정석, author=남궁성, date=2016-01-27)
Book(name=자바의 신, author=이상민, date=2023-10-16)
반응형