(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 이터레이터 패턴이란 (Iterator Patterns)
집합 객체 내부 구조를 노출시키지 않고 순회 하는 방법을 제공하는 패턴
- 반복자 패턴의 본질은 기반이 되는 표현을 노출시키지 않고 연속적으로 객체 요소에 접근하는 방법을 제공하는 것 입니다.
- 다시말해 aggregate 유형에 무관한 동일 순차 접근 방법을 제공하는 것이며, 여기서 aggregate란 반복자객체를 생성하기위한 인터페이스를 정의하는 것이고 iterator 란 요소에 접근 할 수 있고 순회 할 수 있는 인터페이스를 정의하는 것 입니다.
- 👏🏻 쉽게말해, 컬렉션 객체 안에 들어있는 모든 항목에 접근하는 방식이 통일되어 있으면 어떤 종류의 집합체에 대해서도 사용할 수 있는 다형적인 코드를 만들수 있고
- 이러한 이터레이터 패턴을 사용하면 집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있습니다.
- Iterator - 집합체의 요소들을 순서대로 검색하기 위한 인터페이스 정의
- ConcreateIterator - Iterator 인터페이스를 구현함
- Aggregate - 여러 요소들로 이루어져 있는 집합체, Iterator의 역할을 만드는 인터페이스를 정합니다.
- ConcreateAggregate - Aggreagate 인터페이스를 구현하는 클래스
✔️ 이터레이터 패턴 사용 예시
반복자 패턴을 사용하지 않았을 때와 사용하였을 때를, 비교 구현해보겠습니다.
자바의 iterator 인터페이스를 사용하였습니다.
1) 이터레이터 패턴 적용 전
- List를 가지고 있는 Board 클래스가 존재하고, 해당 클래스의 멤버변수인 List를 클라이언트에서 호출하고 순회하는 코드를 작성해보가자 합니다.
@Getter
public class Board {
private final List<Post> posts = new ArrayList<>();
public void addPost(String title) {
posts.add(new Post(title, LocalDateTime.now()));
}
}
@Getter
@AllArgsConstructor
public class Post {
private String title;
private LocalDateTime createdDateTime;
}
- 클라이언트는 Board 클래스의 리스트를 호출해 와, List 형식이라는 것을 알고, 순회하고 있습니다.
- 만약 board 에서 넘겨주는 멤버변수가 List 가 아닌, Tree 나 이중 리스트 등으로 변경되었을 때, 이를 호출하고 순회하고 있는 클라이언트 쪽에서도 모두 변경되어져야 합니다.
- 이러한 문제점을 Iterator패턴을 사용하여 개선할 수 있습니다.
public class Client {
public static void main(String[] args) {
Board board = new Board();
board.addPost("디자인 패턴 게임");
board.addPost("선생님, 저랑 디자인 패턴 하나 학습~ㄱ?");
board.addPost("지금 이자리에 계신 여러분들은 모두 디자인 패턴 학습 ㄱㄱ");
//TODO 들어간 순서대로 순회하기 → 클라이언트가 보드가 list 의 구조라는 것을 알고있어야 하는 순회
// → list 타입이 아닌 다른 무엇인가로 변경된다면 클라이언트 코드도 변경되어야함
List<Post> posts = board.getPosts();
for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
System.out.println(post.getTitle());
}
//TODO 가장 최신 글 먼저 순회하기
Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
}
}
2) 이터레이터 패턴 적용 후
- 가장 단순하게 iterator 를 적용하는 방법입니다.
- Board 가 가지고있는 멤버변수의 리스트를 호출할 때 iterator로 변환해주어 조회합니다.
- 이렇게 해서 Client 가 iterator 를 사용해 호출한다면, 요구사항이 변경되거나 내부구조가 변경되더라도 iterator 타입을 통해서 조회하고 요소들을 소비할 수 있습니다.
public class Client {
public static void main(String[] args) {
//TODO iterator 순회 내부가 List
// Iterator 를 사용하면 보드 내부에 어떤 타입이 존재하는지 알지 않아도 됨
Board board = new Board();
board.addPost("디자인 패턴 게임");
board.addPost("선생님, 저랑 디자인 패턴 하나 학습~ㄱ?");
board.addPost("지금 이자리에 계신 여러분들은 모두 디자인 패턴 학습 ㄱㄱ");
List<Post> posts = board.getPosts();
Iterator<Post> iterator = posts.iterator();
System.out.println(iterator.getClass());
//TODO 가장 최신 글 먼저 순회하기
// board 자체에서 순회하기 위한 Iterator 를 제공하는 방법
Iterator<Post> recentPostIterator = board.getDefaultIterator();
while (recentPostIterator.hasNext()) {
System.out.println(recentPostIterator.next().getTitle());
}
}
}
@Getter
public class Board {
private final List<Post> posts = new ArrayList<>();
public void addPost(String title) {
posts.add(new Post(title, LocalDateTime.now()));
}
public Iterator<Post> getDefaultIterator() {
return posts.iterator();
}
}
만약 Board 가 가지고있는List<Post> 를 생성순으로 정렬하여 조회하고 싶다. 라는 요구사항의 변경이 생성된다면 Board 에서 다른 Iterator 를 반환해주어 변경을 최소화 시킬 수 있습니다.
Board.Class
@Getter
public class Board {
private final List<Post> posts = new ArrayList<>();
public void addPost(String title) {
posts.add(new Post(title, LocalDateTime.now()));
}
public Iterator<Post> getDefaultIterator() {
return posts.iterator();
}
public Iterator<Post> getRecentPostIterator() {
return new RecentPostIterator(posts);
}
}
리스트를 정렬한 iterator를 반환
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();
}
}
Client.class
public class Client {
public static void main(String[] args) {
//생략
//board 의 메소드 부분만 변경하면 됨
Iterator<Post> recentPostIterator = board.getRecentPostIterator();
while (recentPostIterator.hasNext()) {
System.out.println(recentPostIterator.next().getTitle());
}
}
}
→ Board 는 지금 ConcreateAggregate 의 역할을 수행하고 있습니다.
이러한 클라이언트의 변경마저도 최소화 하고 싶다면 Aggreate 인터페이스를 정의하고 클라이언트는 Aggregate인터페이스만을 바라본다면 조금 더 변경을 최소하 하는 구조로 점진적으로 개선해 나갈 수 있을 것 같습니다.
✔️ 이터레이터 패턴 장단점
장점
- 집합체 클래스의 응집도를 높여준다.
- 집합체 내에서 어떤 식으로 일이 처리되는지 알 필요 없이, 집합체 안에 들어있는 모든 항목에 접근 할 수 있게 해준다.
- 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아닌 이터레이터 객체에서 맡게 된다. 이렇게 하면, 집합체의 인터페이스 및 구현이 간단해질 뿐만 아니라, 집합체에서는 반복 작업에서 손을 떼고 원래 자신이 할 일에만 전념할 수 있다.
단점
- 단순한 순회를 구현하는 경우 클래스만 많아져 구조 복잡도가 증가할 수 있다.
이렇게 이터레이터 패턴도 공부해보았습니다!
실무에서 이터레이터 패턴을 어떻게 적용할 수 있을까 고민해보았는데
저라면, 일급 컬렉션 객채로 사용하고 있는 클래스에서, 외부에 조회하고 소비만 하는 리스트를 호출할 때 조금 더 명확하게 iterator 를 제공해준다면 변경에 더 유연하고 응집도가 높은 객체가 되겠구나 생각이 들었습니다
그럼 여기서 끝내겠습니다!
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 - 책임 연쇄 패턴 (Chain of Responsibility Pattern) (0) | 2023.04.11 |
---|---|
[디자인 패턴] 행동 패턴 - 중재자 패턴 (Mediator Pattern) (0) | 2023.04.10 |
[디자인 패턴] 행동 패턴 - 인터프리터 패턴 (Interpreter Pattern) (0) | 2023.03.31 |
[디자인 패턴] 행동 패턴 - 커맨드 패턴 (Command Pattern) (0) | 2023.03.29 |
[디자인 패턴] 구조 패턴 - 프록시 패턴 (Proxy Pattern) (0) | 2023.03.24 |