의도
반복자는 컬렉션의 요소들의 기본 표현(리스트, 스택, 트리 등)을 노출하지 않고 그들을 하나씩 순회할 수 있도록 하는 행동 디자인 패턴입니다.

문제
컬렉션은 프로그래밍에서 가장 많이 사용되는 데이터 유형 중 하나이긴 하지만, 객체 그룹의 단순한 컨테이너에 불과합니다.

다양한 유형들의 컬렉션들.
대부분의 컬렉션들은 그들의 요소들을 간단한 리스트들에 저장하나, 그중 일부는 스택, 트리, 그래프 및 기타 복잡한 데이터 구조들을 기반으로 합니다.
그러나 컬렉션이 어떻게 구성되어 있는지를 떠나서, 컬렉션은 그 요소들에 접근할 수 있는 어떤 방법을 다른 코드에 제공해야 합니다. 그래야 다른 코드가 이 요소들을 사용할 수 있습니다. 같은 요소에 반복해서 접근하지 않고 컬렉션의 각 요소를 순회하는 방법이 있을 것입니다.
리스트로 된 컬렉션이 있다면 아주 쉽게 해결할 수 있을 겁니다. 모든 요소를 루프 처리하면 되니까요. 하지만 트리처럼 복잡한 데이터 구조의 요소들은 어떻게 순차적으로 순회해야 할까요? 예를 들어 어떤 날은 트리의 깊이를 우선으로 순회하는 것이 적절할지도 모릅니다. 하지만 그다음 날에는 너비를 우선으로 순회해야 할 수도 있습니다. 그리고 그다음 주에는 트리 요소들에 대한 임의 접근 등 다른 방식의 순회가 필요할지도 모릅니다.

같은 컬렉션을 여러 가지 방법들로 순회할 수 있습니다.
현재 컬렉션의 주요 책임은 효율적인 데이터 저장이나, 컬렉션에 더 많은 순회 알고리즘들을 추가할수록 컬렉션의 주요 책임이 무엇인지 점점 명확해지지 않게 됩니다. 또한 일부 알고리즘들은 특정 앱에 맞게 조정되었을 수 있으므로 일반적인 컬렉션 클래스에 이들을 포함하는 것은 이상할 수 있습니다.
반면에 다양한 컬렉션들과 작동해야 하는 클라이언트 코드는 자신들의 요소가 어떻게 저장되는지 관심을 두지 않습니다. 하지만 컬렉션마다 그 요소들에 접근할 수 있도록 허용하는 방법이 다르므로, 당신은 코드를 특정한 컬렉션 클래스에 결합할 수밖에 없습니다.
해결책
반복자 패턴의 주 아이디어는 컬렉션의 순회 동작을 반복자라는 별도의 객체로 추출하는 것입니다.

반복자들은 다양한 순회 알고리즘들을 구현합니다. 여러 반복자 객체들이 동시에 같은 컬렉션을 순회할 수 있습니다.
반복자 객체는 알고리즘 자체를 구현하는 것 외에도 모든 순회 세부 정보들(예: 현재 위치 및 남은 요소들의 수)을 캡슐화하며, 이 때문에 여러 반복자들이 서로 독립적으로 동시에 같은 컬렉션을 통과할 수 있습니다.
일반적으로 반복자들은 컬렉션의 요소들을 가져오기 위한 하나의 주 메서드를 제공합니다. 클라이언트는 이 메서드를 더 이상 아무것도 반환하지 않을 때까지 계속 실행할 수 있습니다. 이는 반복자가 모든 요소를 순회했음을 의미합니다.
모든 반복자들은 같은 인터페이스를 구현해야 합니다. 이렇게 하면 적절한 반복자가 있는 한 클라이언트 코드는 모든 컬렉션 유형들 및 순회 알고리즘들과 호환됩니다. 컬렉션을 순회하는 특별한 방법이 필요하면 컬렉션이나 클라이언트를 변경할 필요 없이 새 반복자 클래스를 만들기만 하면 됩니다.
실제상황 적용

도보로 로마를 탐험하는 다양한 방법들
당신은 며칠 동안 로마를 방문하고 주요 명소들을 모두 방문할 계획을 세웠습니다. 그러나 막상 도착한 후 당신은 콜로세움조차 찾지 못하고 제자리를 맴도는 데 많은 시간을 허비할 수 있습니다.
위 사례의 대안으로 스마트폰용 가상 가이드 앱을 구매하여 내비게이션에 사용할 수 있습니다. 이 앱은 똑똑하고 저렴하며 실제 가이드와 달리 원하는 만큼 흥미로운 장소에 머물 수 있도록 합니다.
세 번째 대안은 조금 더 비싸더라도 도시를 잘 아는 현지 가이드를 고용하는 것입니다. 현지 가이드는 당신의 취향에 맞게 여행을 조정하고 모든 명소를 보여주며 흥미진진한 이야기들을 많이 알려줄 수 있습니다. 이 대안을 선택하면 재미는 있겠지만, 더 많은 비용이 들게 될 것입니다.
이 모든 대안들은 (예: 당신이 무작위로 생각해 낸 방향들, 스마트폰 내비게이터, 현지 가이드) 로마의 많은 관광명소에 대해 반복자들로 작동합니다.
/**
* Iterator Design Pattern
*
* Intent: Lets you traverse elements of a collection without exposing its
* underlying representation (list, stack, tree, etc.).
*/
#include <iostream>
#include <string>
#include <vector>
/**
* C++ has its own implementation of iterator that works with a different
* generics containers defined by the standard library.
*/
template <typename T, typename U>
class Iterator {
public:
typedef typename std::vector<T>::iterator iter_type;
Iterator(U *p_data, bool reverse = false) : m_p_data_(p_data) {
m_it_ = m_p_data_->m_data_.begin();
}
void First() {
m_it_ = m_p_data_->m_data_.begin();
}
void Next() {
m_it_++;
}
bool IsDone() {
return (m_it_ == m_p_data_->m_data_.end());
}
iter_type Current() {
return m_it_;
}
private:
U *m_p_data_;
iter_type m_it_;
};
/**
* Generic Collections/Containers provides one or several methods for retrieving
* fresh iterator instances, compatible with the collection class.
*/
template <class T>
class Container {
friend class Iterator<T, Container>;
public:
void Add(T a) {
m_data_.push_back(a);
}
Iterator<T, Container> *CreateIterator() {
return new Iterator<T, Container>(this);
}
private:
std::vector<T> m_data_;
};
class Data {
public:
Data(int a = 0) : m_data_(a) {}
void set_data(int a) {
m_data_ = a;
}
int data() {
return m_data_;
}
private:
int m_data_;
};
/**
* The client code may or may not know about the Concrete Iterator or Collection
* classes, for this implementation the container is generic so you can used
* with an int or with a custom class.
*/
void ClientCode() {
std::cout << "________________Iterator with int______________________________________" << std::endl;
Container<int> cont;
for (int i = 0; i < 10; i++) {
cont.Add(i);
}
Iterator<int, Container<int>> *it = cont.CreateIterator();
for (it->First(); !it->IsDone(); it->Next()) {
std::cout << *it->Current() << std::endl;
}
Container<Data> cont2;
Data a(100), b(1000), c(10000);
cont2.Add(a);
cont2.Add(b);
cont2.Add(c);
std::cout << "________________Iterator with custom Class______________________________" << std::endl;
Iterator<Data, Container<Data>> *it2 = cont2.CreateIterator();
for (it2->First(); !it2->IsDone(); it2->Next()) {
std::cout << it2->Current()->data() << std::endl;
}
delete it;
delete it2;
}
int main() {
ClientCode();
return 0;
}
'디자인 패턴' 카테고리의 다른 글
Memento 패턴 (3) | 2024.09.05 |
---|---|
Mediator 패턴 (0) | 2024.09.05 |
Command 패턴 (0) | 2024.09.04 |
Chain of Responsibility 패턴 (0) | 2024.07.25 |
Proxy 패턴 (0) | 2024.07.25 |