迭代器模式
(Iterator)
迭代器 是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。
1. 问题
集合是编程中最常使用的数据类型之一。尽管如此,集合只是一组对象的容器而已。
各种类型的集合。
大部分集合使用简单列表存储元素。但有些集合还会使用栈、树、图和其他复杂的数据结构。
无论集合的构成方式如何,它都必须提供某种访问元素的方式,便于其他代码使用其中的元素。集合应提供一种能够遍历元素的方式,且保证它不会周而复始地访问同一个元素。
如果你的集合基于列表, 那么这项工作听上去仿佛很简单。但如何遍历复杂数据结构(例如树)中的元素呢?例如,今天你需要使用深度优先算法来遍历树结构,明天可能会需要广度优先算法;下周则可能会需要其他方式(比如随机存取树中的元素)。
可通过不同的方式遍历相同的集合。
不断向集合中添加遍历算法会模糊其“高效存储数据”的主要职责。此外,有些算法可能是根据特定应用订制的,将其加入泛型集合类中会显得非常奇怪。
另一方面,使用多种集合的客户端代码可能并不关心存储数据的方式。不过由于集合提供不同的元素访问方式,你的代码将不得不与特定集合类进行耦合。
2. 解决方案
迭代器模式的主要思想是将集合的遍历行为抽取为单独的迭代器对象。
迭代器可实现多种遍历算法。多个迭代器对象可同时遍历同一个集合。
除实现自身算法外, 迭代器还封装了遍历操作的所有细节,例如当前位置和末尾剩余元素的数量。因此,多个迭代器可以在相互独立的情况下同时访问集合。
迭代器通常会提供一个获取集合元素的基本方法。客户端可不断调用该方法直至它不返回任何内容,这意味着迭代器已经遍历了所有元素。
所有迭代器必须实现相同的接口。这样一来,只要有合适的迭代器, 客户端代码就能兼容任何类型的集合或遍历算法。如果你需要采用特殊方式来遍历集合,只需创建一个新的迭代器类即可,无需对集合或客户端进行修改。
3. 结构
迭代器模式的结构
其中:
*Iterator(迭代器) 定义访问和遍历元素的接口。
*ConcreteIterator(具体迭代器) 实现迭代器接口; 对该聚合遍历时跟踪当前位置。
*Aggregate(聚合) 定义创建相应迭代器对象的接口。
*Concrete Aggregate(具体聚合) 实现创建相应迭代器的接口, 该操作返回ConcreteIterator 的一个适当的实例。
4. 实现方式
1. 声明迭代器接口。
该接口必须提供至少一个方法来获取集合中的下个元素。但为了使用方便,你还可以添加一些其他方法,例如获取前一个元素、记录当前位置和判断迭代是否已结束。
2. 声明集合接口并描述一个获取迭代器的方法。
其返回值必须是迭代器接口。如果你计划拥有多组不同的迭代器,则可以声明多个类似的方法。
3. 为希望使用迭代器进行遍历的集合实现具体迭代器类。
迭代器对象必须与单个集合实体链接。链接关系通常通过迭代器的构造函数建立。
4. 在你的集合类中实现集合接口。
其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
5. 检查客户端代码,使用迭代器替代所有集合遍历代码。
每当客户端需要遍历集合元素时都会获取一个新的迭代器。
5. 代码示例
aggregate.h
#ifndef DESIGN_PATTERNS_AGGREGATE_H
#define DESIGN_PATTERNS_AGGREGATE_H
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//------------------------------//
class Iterator;//迭代器
//------------------------------//
class Aggregate //"合计"类
{
public:
virtual ~Aggregate() {}
virtual Iterator* CreateIterator() = 0;//创建迭代器
};
class List : public Aggregate //"列表"类
{
public:
Iterator* CreateIterator();
int Count();//计数
int operator[] (int) const;//操作
void Insert(int);//插入
private:
vector <int> items_;
};
//------------------------------//
class Iterator //"迭代器"类
{
public:
virtual int First() = 0;
virtual int Next() = 0;
virtual bool IsDone() = 0;//已完成
virtual int CurrentItem() = 0;//当前项
};
class ListIterator : public Iterator //列表迭代器
{
public:
ListIterator() {}
ListIterator(List*);
int First();
int Next();
bool IsDone();//已完成
int CurrentItem();//当前项
private:
int current_;//当前
List *aggregate_;//合计
};
//------------------------------//
#endif //DESIGN_PATTERNS_AGGREGATE_H
aggregate.c
#include "aggregate.h"
//------------------------------//
Iterator* List::CreateIterator()
{
return new ListIterator(this);
}
int List::Count()
{
return (int)items_.size();
}
int List::operator[] (int index) const
{
return items_[index];
}
void List::Insert(int value)
{
items_.push_back(value);
}
//------------------------------//
ListIterator::ListIterator(List *aggregate) : aggregate_(aggregate), current_(0) {}
int ListIterator::First()
{
return (*aggregate_)[0];
}
int ListIterator::Next()
{
int next = -1;
if (++current_ < aggregate_->Count())
next = (*aggregate_)[current_];
return next;
}
bool ListIterator::IsDone()
{
return current_ >= aggregate_->Count();
}
int ListIterator::CurrentItem()
{
return (*aggregate_)[current_];
}
//------------------------------//
Main.c
//------------------------------//
#include <iostream>
#include "aggregate.h"
using namespace std;
//------------------------------//
// Created by Cls on 2024/04/01.
//------------------------------//
int main(int argc, char *argv[])
{
List *list_;
Iterator *list_iterator_;
//-----------------//
list_ = new List();
list_->Insert(1);
list_->Insert(2);
list_->Insert(3);
list_iterator_ = list_->CreateIterator();
//-----------------//
cout << "------------------" << endl;
cout << list_iterator_->CurrentItem() << endl;
cout << list_iterator_->First() << endl;
cout << list_iterator_->Next() << endl;
cout << list_iterator_->IsDone() << endl;
cout << list_iterator_->Next() << endl;
cout << list_iterator_->Next() << endl;
cout << list_iterator_->IsDone() << endl;
cout << "------------------" << endl;
//-----------------//
delete list_;
delete list_iterator_;
//-----------------//
return 0;
}
//------------------------------//
打印输出
6. 应用场景
当集合背后为复杂的数据结构,且你希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
迭代器封装了与复杂数据结构进行交互的细节,为客户端提供多个访问集合元素的简单方法。这种方式不仅对客户端来说非常方便,而且能避免客户端在直接与集合交互时执行错误或有害的操作,从而起到保护集合的作用。
使用该模式可以减少程序中重复的遍历代码。
重要迭代算法的代码往往体积非常庞大。当这些代码被放置在程序业务逻辑中时,它会让原始代码的职责模糊不清,降低其可维护性。因此,将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
如果你希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。
尽该模式为集合和迭代器提供了一些通用接口。如果你在代码中使用了这些接口,那么将其他实现了这些接口的集合和迭代器传递给它时,它仍将可以正常运行。
7. 优缺点
√ 单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类,你可对客户端代码和集合进行整理。
√ 开闭原则。你可实现新型的集合和迭代器并将其传递给现有代码,无需修改现有代码。
√ 你可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态。
√ 相似的,你可以暂停遍历并在需要时继续。
× 如果你的程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
× 对于某些特殊集合,使用迭代器可能比直接遍历的效率低。
8. 与其他模式的关系
• 你可以使用迭代器来遍历组合树。
• 你可以同时使用工厂方法和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
• 你可以同时使用备忘录和迭代器来获取当前迭代器的状态,并且在需要的时候进行回滚。
• 可以同时使用访问者和迭代器来遍历复杂数据结构,并对其中的元素执行所需操作,即使这些元素所属的类完全不同。