迭代器模式是一种行为型设计模式,是一种比较常见的设计模式。我们知道C++的STL中就包含有迭代器(iterator),begin()
就是常见的一种。我们都知道不止vector
、string
等序列式容器有,map
、set
类的关联式容器也有,其主要目的是提供一种统一的方式来访问一个聚合对象中的各个元素。
基本结构
迭代器模式包括一下几个重要角色:
- 迭代器接口
Iterator
:定义访问和遍历元素的接口,通常会包括hasNext()
方法用于检查是否还有下一个元素,以及next()
方法用于获取下个元素。有的还会实现获取第一个元素和当前元素的方法,类比C++中的v.beign()+pos
- 具体迭代器
ConcreteIterator
:实现迭代器接口,实现遍历逻辑对聚合对象进行遍历 - 抽象聚合类:定义了创建迭代器的接口,包括一个
createIterator()
方法创建一个迭代器对象。 - 具体聚合类:实现在抽象聚合类中声明的
createIterator()
方法,返回一个与具体聚合对应的具体迭代器。
我们都知道vector底层是数组,list是链表,set是红黑树,而unordered_map是哈希表。但是我们都可以使用begin()
,end()
去遍历该数据结构,也就是说除实现自身算法外, 迭代器还封装了遍历操作的所有细节, 例如当前位置和末尾剩余元素的数量。 因此, 多个迭代器可以在相互独立的情况下同时访问集合。
从业务的角度说,迭代器通常会提供一个获取集合元素的基本方法。 客户端可不断调用该方法直至它不返回任何内容, 这意味着迭代器已经遍历了所有元素。
所有迭代器必须实现相同的接口。 这样一来, 只要有合适的迭代器, 客户端代码就能兼容任何类型的集合或遍历算法。 如果你需要采用特殊方式来遍历集合, 只需创建一个新的迭代器类即可, 无需对集合或客户端进行修改。
案例
cpp
/**
* 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;
}
执行结果
________________Iterator with int______________________________________
0
1
2
3
4
5
6
7
8
9
________________Iterator with custom Class______________________________
100
1000
10000
上述代码中使用了模板编程,我们可以看到Iterator
类其实就是抽象迭代器类,迭代器begin()
和end()
是STL提供的,然后Container
类是抽象的聚合类,其中定义了创建迭代器的接口createIterator
,Data
类是自定义的数据类型,案例中还使用了int
。
接着,在客户端代码中,创建了具体数据类型的Container
和Iterator
,使用First()
、Next()
的接口进行遍历,输出对应的信息。总体来说,通过迭代器模式实现了对于遍历细节的隐藏,这种统一的遍历方式也体现了面向对象的封装特点。此外,通过Iterator
类和原对象解耦,增强了代码的通用性,也更符合开闭原则。
优缺点
优点:
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
- 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
- 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
- 相似的, 你可以暂停遍历并在需要时继续。
缺点: - 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。