设计模式:迭代器模式(Iterator)
- 设计模式:迭代器模式(Iterator)
- 模式动机
- 模式定义
- 模式结构
- 时序图
- 模式实现
- 在单线程环境下的测试
- 在多线程环境下的测试
- 模式分析
- 优缺点
- 适用场景
- 应用场景
- 参考
设计模式:迭代器模式(Iterator)
迭代器模式(Iterator)属于行为型模式(Behavioral Pattern)的一种。
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
行为型模式分为类行为型模式和对象行为型模式两种:
- 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
- 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
模式动机
我们使用的聚合对象各种各样,比如vector、list、tree、map等等,既然是聚合,那就有访问其个体的需要。而遍历访问这个行为可能有深度优先、广度优先、顺序遍历、逆序遍历等等,迭代器的意义就是将这个行为抽离封装起来,这样客户端只需要调用合适的迭代器,来进行对应的遍历,而不用自己去实现这一行为。
模式定义
迭代器模式(Iterator)属于行为型模式。
迭代器模式(Iterator)提供一种方法顺序访问一个聚合对象中各个元素,而不暴露该对象的内部表示。通过迭代器,客户端可以顺序访问聚合对象的元素,而无需了解底层数据结构。
模式结构
迭代器模式(Iterator)包含如下角色:
- 抽象聚合(Aggregate):抽象容器,定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate):具体容器,实现内部不同结构,返回一个迭代器实例。
- 抽象迭代器(Iterator): 定义访问和遍历聚合元素的接口。
- 具体迭代器(ConcreteIterator): 实现抽象迭代器接口中的方法,完成对聚合对象的遍历,记录遍历的当前位置。
从上图中,我们可以看到具体容器和具体迭代器两者是相互调用的,也就是说容器调用了迭代器,然后通过迭代器调用了容器内部自身的方法来实现封装。
时序图
略。
模式实现
以 vector 为例,设计了模板类。示例代码如下:
抽象迭代器 Iterator.h:
#ifndef _ITERATOR_H_
#define _ITERATOR_H_
template<typename T>
class Iterator
{
public:
// 第一个元素
virtual T first() = 0;
// 下一个元素
virtual T next() = 0;
// 是否到达容器的终点
virtual bool isDone() = 0;
// 获取当前元素的指针
virtual T* currentItem() = 0;
};
#endif // !_ITERATOR_H_
具体迭代器 ConcreteIterator.h:
#ifndef _CONCRETE_ITERATOR_H_
#define _CONCRETE_ITERATOR_H_
#include <vector>
#include "Iterator.h"
template<typename T>
class ConcreteIterator : public Iterator<T>
{
private:
std::vector<T> m_data;
int index;
public:
// 构造函数
ConcreteIterator(std::vector<T> data) : m_data(data), index(0) {}
T first()
{
return *m_data.begin();
}
T next()
{
if (isDone())
return (T)0;
return m_data[index++];
}
bool isDone()
{
if (index < m_data.size())
return false;
return true;
}
T* currentItem()
{
if (index < m_data.size())
return &m_data[index];
else
return nullptr;
}
};
#endif // !_CONCRETE_ITERATOR_H_
抽象聚合 Aggregate.h:
#ifndef _AGGREGATE_H_
#define _AGGREGATE_H_
#include "Iterator.h"
#include "Aggregate.h"
template<typename T>
class Aggregate
{
public:
// 创建迭代器
virtual Iterator<T>* createIterator() = 0;
};
#endif // !_AGGREGATE_H_
具体聚合 ConcreteAggregate.h:
#ifndef _CONCRETE_AGGREGATE_H_
#define _CONCRETE_AGGREGATE_H_
#include "ConcreteIterator.h"
#include "Aggregate.h"
template<typename T>
class ConcreteAggregate : public Aggregate<T>
{
private:
std::vector<T> m_data;
public:
// 构造函数
ConcreteAggregate(std::vector<T> data) : m_data(data) {}
Iterator<T>* createIterator()
{
return new ConcreteIterator<T>(m_data);
}
};
#endif // !_CONCRETE_AGGREGATE_H_
在单线程环境下的测试
测试代码,也可以说是 client:
#include <iostream>
#include <vector>
#include <stdlib.h>
#include "ConcreteIterator.h"
#include "ConcreteAggregate.h"
using namespace std;
int main()
{
vector<int> data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 创建容器和迭代器
ConcreteAggregate<int>* aggregate = new ConcreteAggregate<int>(data);
Iterator<int>* iterator = aggregate->createIterator();
// 迭代器输出
while (!iterator->isDone())
cout << iterator->next() << " ";
cout << endl;
// 清除
delete iterator;
delete aggregate;
iterator = nullptr;
aggregate = nullptr;
system("pause");
return 0;
}
运行结果:
在多线程环境下的测试
略。
模式分析
- 迭代器模式提供了一种统一的方式来遍历集合对象中的元素。
- 它将遍历操作封装到一个独立的迭代器对象中,使得我们可以按照特定的方式访问集合中的元素。
- 迭代器模式将集合对象和遍历操作分离开来,提高了代码的灵活性和可维护性。
- 使用迭代器模式可以让我们用相同的方式遍历不同类型的集合对象,而不需要了解集合的内部结构。
优缺点
优点:
- 符合单一职责原则。将遍历行为抽离成单独的类。
- 符合开闭原则。如果需要增加新的遍历方式,只需实现一个新的具体迭代器即可,不需要修改原先聚合对象的代码。
- 简化了集合类的接口,使用者可以更加简单地遍历集合对象,而不需要了解集合内部结构和实现细节。
- 将集合和遍历操作解耦,使得我们可以更灵活地使用不同的迭代器来遍历同一个集合,根据需求选择不同的遍历方式。
缺点:
- 不适合每种场景:若对聚合对象只需要进行简单的遍历行为,那使用迭代器模式有些大材小用。
- 遍历效率降低:在某些情况下,使用迭代器模式可能会导致遍历效率降低,特别是使用外部迭代器时,需要手动进行迭代操作。
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
适用场景
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 减少与集合对象的耦合。
- 为遍历不同的聚合结构提供一个统一的接口。
- 提供一个便利的方法访问一个聚合对象中的每个元素。
应用场景
- 集合框架中的迭代器:C++ 的 STL 容器一般都有自己的迭代器。在Java中,集合包括List、Set、Map等等,每个集合类中都提供了一个获取迭代器的方法,例如List提供的iterator()方法、Set提供的iterator()方法等等。通过获取对应的迭代器对象,可以对集合中的元素进行遍历和访问。
- JDBC中的ResultSet对象:在Java中,如果需要对数据库中的数据进行遍历和访问,可以使用JDBC操作数据库。JDBC中,查询结果集使用ResultSet对象来表示,通过使用ResultSet的next()方法,就可以像使用迭代器一样遍历和访问查询结果中的数据。
- 文件读取:在Java中,我们可以使用BufferedReader类来读取文本文件。BufferedReader类提供了一个方法readLine()来逐行读取文件内容。实际上,BufferedReader在内部使用了迭代器模式来逐行读取文本文件的内容。
- Python 使用迭代器和⽣成器来实现迭代模式,iter() 和next() 函数可以⽤于创建和访问迭代器。
- JavaScript:ES6中新增了迭代器协议,使得遍历和访问集合元素变得更加方便。
参考
- https://zhuanlan.zhihu.com/p/678005837
- https://blog.csdn.net/zhaitianbao/article/details/130097933
- https://hermit.blog.csdn.net/article/details/123429647
- https://www.cnblogs.com/ybqjymy/p/17534782.html
- https://blog.csdn.net/qq_42764269/article/details/127382043
- https://blog.csdn.net/K1_uestc/article/details/135772598
- https://blog.csdn.net/weixin_45433817/article/details/131382881
- https://www.runoob.com/design-pattern/iterator-pattern.html