概述
与上一篇介绍的解释器模式一样,迭代器模式也是一种行为设计模式。它提供了一种方法来顺序访问一个聚合对象中的各个元素,而无需暴露该对象的内部表示。简而言之,迭代器模式允许我们遍历集合数据结构中的元素,而不必了解这些集合的底层实现细节。
音乐播放器是运用迭代器模式的一个典型例子:当我们使用音乐播放器听歌时,通常会有"下一首"、"上一首"的功能来切换歌曲。这里的歌曲播放列表就相当于一个聚合对象,而用于切换歌曲的功能就是迭代器。用户可以轻松地遍历整个播放列表,而无需了解歌曲是如何存储或排序的。

基本原理
迭代器模式的核心思想在于:提供一种相对"透明"的方法,以便顺序访问聚合对象中的每一个元素。通过这种方法,迭代器模式实现了遍历逻辑与数据结构之间的解耦,使得不同的聚合对象可以使用相同的遍历逻辑。另外,迭代器模式还支持多种元素遍历的策略。
迭代器模式主要由以下四个核心组件构成。
1、聚合接口。定义创建相应迭代器对象的接口,通常包含一个CreateIterator方法,用于返回一个迭代器实例。
2、具体聚合。实现了聚合接口,并负责生成一个具体的迭代器对象,这个迭代器能够遍历与该具体聚合相关的集合。具体聚合不仅持有集合的数据,还可能包含添加、删除元素等操作。
3、迭代器接口。定义了访问和遍历元素的接口,通常包括HasNext和Next两个基本方法。前者用来判断是否还有下一个元素,后者则返回当前元素,并指向下一个位置。还可能包括Remove方法,用于移除当前元素。
4、具体迭代器。实现了迭代器接口,并跟踪遍历的状态。具体迭代器必须记住它所遍历的聚合对象,并提供访问该聚合对象元素的方法。比如:具体迭代器可能会保存一个索引或指针,用以追踪当前遍历到的位置。
基于上面的核心组件,迭代器模式的实现主要有以下四个步骤。
1、定义聚合接口。首先需要定义一个聚合接口,其中至少包含一个CreateIterator方法。这个方法的作用是:为所有具体聚合类提供一种通用的方式来创建相应的迭代器。
2、实现具体聚合。实现一个或多个具体聚合类,这些类实现了上述聚合接口。每个具体聚合类都应包含自己的数据集合,并且要实现CreateIterator方法。
3、定义迭代器接口。定义一个迭代器接口,该接口应该包括遍历所需的基本方法,如HasNext和Next。如果有需要,还可以包括Remove方法。
4、实现具体迭代器。最后,我们需要为每个具体聚合实现一个具体的迭代器类。每个具体迭代器都应该实现迭代器接口,并维护遍历过程中的状态信息,比如:当前遍历的位置。
实战解析
在下面的实战代码中,我们使用迭代器模式模拟了音乐播放器的实现。
首先,我们定义了两个接口:Iterator和SongCollection。前者规定了迭代器应具备的基本操作,后者则定义了创建迭代器的方法CreateIterator。
接着,我们定义了具体聚合类ConcreteSongCollection。它实现了SongCollection接口,并包含一个私有的成员变量m_vctSong,用于存储歌曲列表。同时,声明了ConcreteIterator为友元类,以便直接访问其私有成员。
具体迭代器类ConcreteIterator实现了Iterator接口,维护对具体聚合对象的引用及当前迭代位置的迭代器m_itrCur。ConcreteIterator提供了具体的迭代逻辑,包括判断是否还有下一首歌、获取下一首歌、重置到列表开头的功能。
最后,PlayMusic函数模拟了音乐播放功能,通过调用迭代器的方法遍历并播放所有歌曲。在main函数中,实例化了具体的歌曲集合ConcreteSongCollection,添加了几首歌曲,并创建了对应的迭代器来执行播放操作。
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 迭代器接口
class Iterator
{
public:
virtual bool HasNext() = 0;
virtual string Next() = 0;
virtual void Reset() = 0;
};
// 聚合接口
class SongCollection
{
public:
virtual Iterator* CreateIterator() = 0;
};
// 具体聚合
class ConcreteSongCollection : public SongCollection
{
friend class ConcreteIterator;
public:
void AddSong(const string& strSong)
{
m_vctSong.push_back(strSong);
}
Iterator* CreateIterator() override;
private:
vector<string> m_vctSong;
};
// 具体迭代器
class ConcreteIterator : public Iterator
{
public:
ConcreteIterator(const ConcreteSongCollection& collection)
: m_collection(collection), m_itrCur(m_collection.m_vctSong.begin()) {}
bool HasNext() override
{
return m_itrCur != m_collection.m_vctSong.end();
}
string Next() override
{
if (HasNext())
{
return *(m_itrCur++);
}
return "";
}
void Reset() override
{
m_itrCur = m_collection.m_vctSong.begin();
}
private:
ConcreteSongCollection m_collection;
vector<string>::const_iterator m_itrCur;
};
Iterator* ConcreteSongCollection::CreateIterator()
{
return new ConcreteIterator(*this);
}
// 模拟音乐播放器播放歌曲列表
void PlayMusic(SongCollection& collection, Iterator* pIterator)
{
cout << "Start playing music..." <<endl;
while (pIterator->HasNext())
{
string strSong = pIterator->Next();
cout << "Now playing: " << strSong <<endl;
}
// 回到第一个元素
pIterator->Reset();
}
int main()
{
ConcreteSongCollection collection;
collection.AddSong("Hey Jude");
collection.AddSong("Imagine");
collection.AddSong("Shape of You");
Iterator* pIterator = collection.CreateIterator();
PlayMusic(collection, pIterator);
delete pIterator;
return 0;
}
总结
迭代器模式将遍历集合的逻辑从聚合对象中分离出来,使得聚合对象不需要关心如何遍历其内部的数据结构。这增强了代码的封装性,减少了模块间的耦合度。由于遍历逻辑被移到了迭代器中,聚合对象的接口变得更加简洁明了,只需要提供创建迭代器的方法即可,而无需暴露复杂的遍历逻辑或内部实现细节。另外,同一聚合对象可以通过不同的迭代器实现不同的遍历方式(比如:顺序遍历、逆序遍历等),增加了系统的灵活性和适应性。
但引入迭代器模式可能会导致系统复杂度上升,尤其是在处理简单集合时,使用迭代器可能显得过于繁琐,增加了不必要的间接层。在某些情况下,尤其是当集合非常庞大时,创建多个迭代器实例可能会消耗较多的内存资源。特别要注意的是:如果迭代器在遍历时,修改了集合的内容,可能会引发一致性问题。