반응형
의도
- 집합 객체 내부 구조를 노출시키지 않고 순회 하는 방법을 제공하는 패턴
- 내부 표현부를 노출하지 않고 어떤 집합 객체에 속한 원소들을 순차적으로 접근할 수 있는 방법을 제공한다.
장점
- 집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있다.
- 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있다
- 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있다.
단점
- 클래스가 늘어나고 복잡도가 증가한다.
알려진 사용 예
- 자바
- java.util.Enumeration
- 과거의 클래스. Iterator로 기능이 대체 되었다.
- java.util.Iterator
- remove
- 동시성 문제가 없는 Collection에서 지원되고 문제가 생길 가능성이 있는 곳에서는 지원하지 않는 기능
- forEachRemaining
- Consumer 함수형 인터페이스 사용
- 순회 결과를 하나씩 받아서 처리 가능
- remove
- Java StAX(Streaming API for XML)의 Iterator 기반 API
- XML을 읽고 쓰기가 가능하다.
- 콘솔 기반의 API, 이터레이터 기반의 API를 제공한다.
- 콘솔 기반의 API
- 하나의 인스턴스가 지나가면서 내용이 바뀐다.(상태변경)
- 메모리는 이터레이터 기반보다 효율적이다. 그러나 재사용, 변경 측면에서는 좋지 않다.
- 유연성 있게 되려면 상태가 바뀌기 보다는 하나 하나 제각각의 XML을 표현하는 불변객체를 사용하는 것이 좋다.
- 이터레이터 기반의 API
- XmlEventReader, XmlEventWriter
- 이벤트가 XML 엘리먼트당 하나씩 지나간다. 인스턴스가 엘리먼트당 만들어진다. 이를 이용한다.
- 하나 하나 제각각의 XML을 표현하는 불변객체이기 떄문에 유연한 처리가 가능하다.
- 일반적으로 유연한 이터레이터 기반의 API를 사용하는 것을 권장한다.
- XML을 읽고 쓰기가 가능하다.
- java.util.Enumeration
- 스프링
- CompositeIterator
- Iterator 여러 개를 조합해서 사용한다.
- CompositeIterator
활용성
- 집합 객체 내부의 표현 방식이 변경 가능성이 있을 때
- 클라이언트가 집합 객체 내부 표현 방식을 알고 있다면, 표현 방식(List에서 Set으로 등)이 달라지면 클라이언트 코드도 변경되어야 하는 문제가 생긴다.
- 객체 내부 표현 방식을 모르고도 집합 객체의 각 원소들에 접근하고 싶을 때
- 집합 객체를 순회하는 다양한 방법을 지원하고 싶을 때
- 서로 다른 집합 객체 구조에 대해서도 동일한 방법으로 순회하고 싶을 때
결과
- 집합 객체의 다양한 순회 방법을 제공한다.
- 구조가 복잡한 집합 객체는 다양한 방법으로 순회할 수 있다.
- 예) 컴파일러에서 코드 생성 및 의미 점검(semantic checking)을 진행하려면 파스 트리를 순회해야 한다.
- 코드를 생성하기 위해 트리를 순회할 때 중위 순회 방식이나 전위 순회 방식을 사용할 수 있다.
- 이 때, Iterator는 순회 알고리즘을 바꿀 수 있도록 한다.
- 단, 순회 방법이 추가되는 경우 집합 객체의 코드는 변경될 수 있다. 해당 순회 방법을 지원하기 위해 코드가 추가되어야 한다.
- Iterator는 Aggregate 클래스의 인터페이스를 단순화한다.
- Iterator의 순회 인터페이스는 Aggregate 클래스에 정의한 자신과 비슷한 인터페이스들을 없애서 Aggregate 인터페이스를 단순화할 수 있다.
- 집합 객체에 따라 하나 이상의 순회 방법이 제공될 수 있다.
- 각 Iterator마다 자신의 순회 상태가 있으므로 하나의 집합 객체를 한번에 여러 번 순회시킬 수 있다.
협력 방법
- ConcreteIterator는 집합 객체 내 현재 객체를 계속 추적하고 다음번 방문할 객체를 결정한다.
구조
실제 구현 구조
소스코드
//ConcreteAggregate
public class Board {
List<Post> posts = new ArrayList<>();
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
public void addPost(String content) {
this.posts.add(new Post(content));
}
public Iterator<Post> iterator() {
return new RecentPostIterator(this.getPosts());
}
}
//ConcreteIterator
public class RecentPostIterator implements Iterator<Post> {
private Iterator<Post> internalIterator;
public RecentPostIterator(List<Post> posts) {
Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
this.internalIterator = posts.iterator();
}
@Override
public boolean hasNext() {
return this.internalIterator.hasNext();
}
@Override
public Post next() {
return this.internalIterator.next();
}
}
public class Post {
private String title;
private LocalDateTime createdDateTime;
public Post(String title) {
this.title = title;
this.createdDateTime = LocalDateTime.now();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public LocalDateTime getCreatedDateTime() {
return createdDateTime;
}
public void setCreatedDateTime(LocalDateTime createdDateTime) {
this.createdDateTime = createdDateTime;
}
}
public class Client {
public static void main(String[] args) {
Board board = new Board();
board.addPost("포스트1");
board.addPost("포스트2");
board.addPost("포스트3");
//가장 최신 글 먼저 순회하기
Iterator<Post> postIterator = board.iterator();
while (postIterator.hasNext()) {
Post post = postIterator.next();
System.out.println(post.getTitle());
}
}
}
관련 패턴
- 반복자 패턴은 복합체 패턴과 같이 재귀적 구조가 있을 떄 자주 사용한다.
- 다양한 반복자를 사용해서 적당한 Iterator 서브클래스를 얻으려면 팩토리 메서드 패턴을 사용할 수 있다.
- 반복자 자신이 반복한 결과를 저장하기 위해서 메멘토 패턴도 반복자 패턴과 함께 자주 사용된다.
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
반응형