iterator pattern
정의 : 객체 지향 프로그래밍에서 반복자를 사용하여 컨테이너를 가로지르며 컨테이너의 요소들에 접근하는 디자인 패턴이다. 반복자 패턴은 컨테이너로부터 알고리즘을 분리시키며, 일부의 경우 알고리즘들은 필수적으로 컨테이너에 특화되어 있기 때문에 분리가 불가능하다.
-> 서로 다른 자료구조들을 같은 방법으로 순회하고 싶다.
#include <iostream>
int main()
{
std::List<int> s = { 1,2,3,4,5 }; // 모든 요소가 떨어진 메모리
std::vector<int> v = { 1,2,3,4,5 }; // 모든 요소가 연속된 메모리
}
list는 보통 double linked list를 사용하여 구현되어있다 한다. 즉 요소 요소가 떨어져 있다.
[ ←o→ ←o→ ←o→ ←o→ ]
vector는 각 요소들이 연속적인 공간에 배치되어 있다.
[oooo]
사실 위 두 개가 메모리 상에서 어떤 방식으로 구현되어 있건간에.. 같은 방법으로 내부 요소들을 순회할 수 있으면 좋을 것이다.
만약 하나의 구조체마다 다른 순회 방법이 있으면 상당히 많이 복잡할테니...
다행히도, 내부 구조가 어떻게 되어있든간에 우리는 하나의 방법으로 안의 요소들을 순회할 수 있다.
for (const auto& elm : list){
std::cout << elm << std::endl;
}
이런 느낌으로... cpp에서는 이미 iterator를 통해 간단하게 다른 구조의 요소들을 순회할 수 있다.
만약 우리가 새로운 방식의 구조체를 만들고, 그 구조체들 또한 같은 방법으로 순회하고 싶으면 어떻게 해야 할까?
MyArray s;
MyArray2 v;
auto p1 = s.makeIterator(); // MyArray의 iterator
auto p2 = v.makeIterator(); // MyArray2의 iterator
int n1 = p1.getObject(); // 요소 얻기
int n2 = p2.getObject();
p1.moveNext(); // 다음으로 이동
p2.moveNext();
template<typename T>
struct iterator
{
virtual T& getObject() = 0;
virtual bool moveNext() = 0;
virtual ~iterator() {}
};
template<typename T>
struct iterable
{
virtual iterator<T>* makeIterator() = 0;
virtual ~iterable() {}
};
이 노드들을 기반으로 linked list container를 만들면
template<typename T>
struct Node
{
T data;
Node* next;
Node(const T& d, Node* n) : data(d), next(n) {}
};
위처럼 된다.
위 두 개를 가지고 single linked list를 만들어 보자.
그렇다면 먼저 single linked list 자체와, single linkedlist의 iterator. 두 개가 필요할 것이다.
// single linked list에서 돌아가는 iterator를 제작한다.
template<typename T>
class slist_iterator : public iterator<T>
{
Node<T>* current;
public:
slist_iterator(Node<T>* p = nullptr) : current(p) {}
T& getObject() override
{
return current->data;
}
bool moveNext() override
{
current = current->next;
return current;
}
};
// iterable 을 상속받는다.
template<typename T>
struct slist : public iterable<T>
{
Node<T>* head = nullptr;
public:
void push_front(const T& a) {head = new Node<T>(a, head);}
iterator<T>* makeIterator() override
{
return new slist_iterator<T>(head);
}
};
이렇게 하면, 사용자는 구조체의 내부가 어떻게 되어있는지 신경쓰지 않고 구조체를 순회할 수 있게 된다.
int main()
{
slist<int> s;
s.push_front(10);
s.push_front(20);
s.push_front(30);
s.push_front(40);
iterator<int>* p = s.makeIterator();
do
{
std::cout << p->getObject() << std::endl;
} while (p->moveNext());
delete p;
}
다만.. 열심히 만들었지만 cpp 맨 처음에 예를 들었던 std::list / std::vector에서는 이러한 방식의 iterator를 사용하지 않고 다른 방식의 iterator를 사용한다고 한다.
몇 가지 이유가 있는데.
#include <iostream>
template<typename T>
struct Node {
T data;
Node* next;
Node(const T& d, Node* n) : data(d), next(n){}
}
template<typename T>
class slist_iterator {
Node<T>* current;
public:
slist_iterator(Node<T>* d = nullptr) : current(d){}
inline T& operator*(){
return current->data;
}
inline slist_iterator<T>& operator++(){
current = current->next;
return *this;
}
inline bool operator==(const slist_iterator& other) const{
return current == other.current;
}
inline bool operator!=(const slist_iterator& other) const{
return !(current == other.current);
}
}
template <typename T>
struct slist{
Node<T>* head {nullptr};
public:
void push_front(const T& a){
head = new Node<T>(a, head);
}
using iterator = slist_iterator<T>;
iterator begin() { return iterator(head); }
iterator end() { return iterator(nullptr); }
}
int main()
{
slist<int> s;
s.push_front(10);
s.push_front(20);
s.push_front(30);
s.push_front(40);
slist<int>::iterator p = s.begin();
while (p != s.end())
{
std::cout << *p << std::endl;
++p;
}
}
* 잘못된 정보는 알려주시면 감사하겠습니다.
'Design Pattern' 카테고리의 다른 글
[Design Pattern] state pattern (0) | 2023.05.19 |
---|---|
flyweight pattern (0) | 2022.12.01 |
Template Method (0) | 2022.09.06 |
Observer Pattern (0) | 2022.08.14 |
Decorator pattern (0) | 2022.07.26 |