컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 반복자(iterator)를 사용하여 접근할 수 있는 패턴으로 내부구조를 노출하지 않고, 복잡 객체의 원소를 순차적으로 접근 가능하게 해준다.
컬렉션(List, Set, Map, Queue)객체에 접근할 때 List나 배열과 같은 순차적으로 배열된 집합체는 간단한 for문을 통해 순회할 수 있다. 그러나 해시, 트리와 같이 데이터 저장 순서가 정해지지 않고 적재되기 때문에, 각 요소들을 어떤 기준으로 접근해야 할지 애매해진다.
이럴 때 반복자 패턴을 사용하면 컬렉션 객체 안에 들어있는 모든 원소들에 대한 접근 방식이 공통화 되어 있다면 어떤 종류의 컬렉션에서도 이터레이터만 뽑아내면 여러 전략으로 순회가 가능해 보다 다형(多形) 적인 코드를 설계할 수 있게 된다.
- Aggregate (인터페이스) : ConcreateIterator 객체를 반환하는 인터페이스를 제공한다.
iterator() : ConcreateIterator 객체를 만드는 팩토리 메서드- ConcreateAggregate (클래스) : 여러 요소들이 이루어져 있는 데이터 집합체
- Iterator (인터페이스) : 집합체 내의 요소들을 순서대로 검색하기 위한 인터페이스를 제공한다.
hasNext() : 순회할 다음 요소가 있는지 확인 (true / false)
next() : 요소를 반환하고 다음 요소를 반환할 준비를 하기 위해 커서를 이동시킴- ConcreateIterator (클래스) : 반복자 객체
ConcreateAggregate가 구현한 메서드로부터 생성되며, ConcreateAggregate 의 컬렉션을 참조하여 순회한다.
어떤 전략으로 순회할지에 대한 로직을 구체화 한다.
// Post.java
public class Post {
String title;
LocalDate date;
public Post(String title, LocalDate date) {
this.title = title;
this.date = date;
}
}
// Board.java
public class Board {
List<Post> posts = new ArrayList<>();
public void addPost(String title, LocalDate date) {
posts.add(new Post(title, date));
}
public List<Post> getPosts() {
return posts;
}
}
// Client.java
public class Client {
public static void main(String[] args) {
Board board = new Board();
board.addPost("그그그 뭐더라 뭐 쓰려고했더라", LocalDate.of(2024, 9, 7));
board.addPost("안녕하세요 프리미어리그 득점왕 손흥민입니다.", LocalDate.of(2021, 8, 7));
board.addPost("아는 형님의 삼촌의 누님의 오빠가 나일 확률은?", LocalDate.of(2023, 12, 26));
board.addPost("디자인패턴 세개 삽니다.", LocalDate.of(2022, 5, 5));
List<Post> posts = board.getPosts();
for (Post post : posts) {
System.out.println(post.title + " / " + post.date);
}
Collections.sort(posts, new Comparator<Post>() {
public int compare(Post p1, Post p2) {
return p1.date.compareTo(p2.date);
}
});;
System.out.println();
for (Post post : posts) {
System.out.println(post.title + " / " + post.date);
}
}
}
// 실행 결과
그그그 뭐더라 뭐 쓰려고했더라 / 2024-09-07
안녕하세요 프리미어리그 득점왕 손흥민입니다. / 2021-08-07
아는 형님의 삼촌의 누님의 오빠가 나일 확률은? / 2023-12-26
디자인패턴 세개 삽니다. / 2022-05-05안녕하세요 프리미어리그 득점왕 손흥민입니다. / 2021-08-07
디자인패턴 세개 삽니다. / 2022-05-05
아는 형님의 삼촌의 누님의 오빠가 나일 확률은? / 2023-12-26
그그그 뭐더라 뭐 쓰려고했더라 / 2024-09-07
일반적으로 for문을 돌려 집합체의 요소들을 순회하였다. 그러나 이러한 구성 방식은 Board에 들어간 Post를 순회할 때, Board가 어떠한 구조로 이루어져 있는지를 클라이언트에 노출된다. 따라서 이를 보다 객체 지향적으로 구성하기 위해 이터레이터 패턴을 적용해보자.
// Post.java (반복자 패턴의 대상이 되는 단일 클래스)
public class Post {
String title;
LocalDate date;
public Post(String title, LocalDate date) {
this.title = title;
this.date = date;
}
}
// Board.java (ConcreteAggregate)
public class Board {
List<Post> posts = new ArrayList<>();
public void addPost(String title, LocalDate date) {
posts.add(new Post(title, date));
}
public List<Post> getPosts() {
return posts;
}
public Iterator<Post> getListPostIterator() {
return new ListPostIterator(posts);
}
public Iterator<Post> getDatePostIterator() {
return new DatePostIterator(posts);
}
}
/// ListPostIterator.java (ConcreteIterator)
public class ListPostIterator implements Iterator<Post> {
private Iterator<Post> itr;
public ListPostIterator(List<Post> posts) {
this.itr = posts.iterator();
}
@Override
public boolean hasNext() {
return itr.hasNext();
}
@Override
public Post next() {
return itr.next();
}
}
// DatePostIterator.java (ConcreteIterator)
public class DatePostIterator implements Iterator<Post> {
private Iterator<Post> itr;
public DatePostIterator(List<Post> posts) {
Collections.sort(posts, new Comparator<Post>() {
public int compare(Post p1, Post p2) {
return p1.date.compareTo(p2.date);
}
});;
this.itr = posts.iterator();
}
@Override
public boolean hasNext() {
return itr.hasNext();
}
@Override
public Post next() {
return itr.next();
}
}
// Client.java (Client)
public class Client {
public static void main(String[] args) {
Board board = new Board();
board.addPost("그그그 뭐더라 뭐 쓰려고했더라", LocalDate.of(2024, 9, 7));
board.addPost("안녕하세요 프리미어리그 득점왕 손흥민입니다.", LocalDate.of(2021, 8, 7));
board.addPost("아는 형님의 삼촌의 누님의 오빠가 나일 확률은?", LocalDate.of(2023, 12, 26));
board.addPost("디자인패턴 세개 삽니다.", LocalDate.of(2022, 5, 5));
print(board.getListPostIterator());
System.out.println();
print(board.getDatePostIterator());
}
public static void print(Iterator<Post> iterator) {
Iterator<Post> itr = iterator;
while(itr.hasNext()) {
Post post = itr.next();
System.out.println(post.title + " / " + post.date);
}
}
}
이제 클라이언트는 게시글을 순회할 때 Board 내부가 어떤 집합체로 구현(Array, List, Tree, Queue ..등) 되어 있는지 알 수 없게 감추고 전혀 신경 쓸 필요가 없게 되었다. 그리고 순회 전략을 각 객체로 나눔으로써 때에 따라 적절한 이터레이터 객체만 받으면 똑같은 이터레이터 순회 코드로 다양한 순회 전략을 구사할 수 있게 되었다.
Iterator를 사용하는 또다른 이유는 이터레이터를 사용함으로써 집합체 구현과 분리할 수 있기 때문이다.
Iterator<Post> itr = iterator;
while(itr.hasNext()) {
Post post = itr.next();
System.out.println(post.title + " / " + post.date);
}
이터레이터 객체를 반환하면 컬렉션을 순회할때 hasNext() 와 next() 라는 Iterator의 메소드만을 이용하기 때문에, 집합체인 Board의 내부 구성을 감출수 있게 된다. 즉, 위의 while문은 Board의 구현에 의존하지 않는 것이다.
이 말은 만일 추후에 Board의 집합체를 수정하더라도 Board 클래스가 올바른 Iterator만을 반환해 준다면 클라이언트의 코드(위의 while 루프)는 변경하지 않아도 되게 된다.