实战设计模式之迭代器模式

概述

与上一篇介绍的解释器模式一样,迭代器模式也是一种行为设计模式。它提供了一种方法来顺序访问一个聚合对象中的各个元素,而无需暴露该对象的内部表示。简而言之,迭代器模式允许我们遍历集合数据结构中的元素,而不必了解这些集合的底层实现细节。

音乐播放器是运用迭代器模式的一个典型例子:当我们使用音乐播放器听歌时,通常会有"下一首"、"上一首"的功能来切换歌曲。这里的歌曲播放列表就相当于一个聚合对象,而用于切换歌曲的功能就是迭代器。用户可以轻松地遍历整个播放列表,而无需了解歌曲是如何存储或排序的。

基本原理

迭代器模式的核心思想在于:提供一种相对"透明"的方法,以便顺序访问聚合对象中的每一个元素。通过这种方法,迭代器模式实现了遍历逻辑与数据结构之间的解耦,使得不同的聚合对象可以使用相同的遍历逻辑。另外,迭代器模式还支持多种元素遍历的策略。

迭代器模式主要由以下四个核心组件构成。

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;
}

总结

迭代器模式将遍历集合的逻辑从聚合对象中分离出来,使得聚合对象不需要关心如何遍历其内部的数据结构。这增强了代码的封装性,减少了模块间的耦合度。由于遍历逻辑被移到了迭代器中,聚合对象的接口变得更加简洁明了,只需要提供创建迭代器的方法即可,而无需暴露复杂的遍历逻辑或内部实现细节。另外,同一聚合对象可以通过不同的迭代器实现不同的遍历方式(比如:顺序遍历、逆序遍历等),增加了系统的灵活性和适应性。

但引入迭代器模式可能会导致系统复杂度上升,尤其是在处理简单集合时,使用迭代器可能显得过于繁琐,增加了不必要的间接层。在某些情况下,尤其是当集合非常庞大时,创建多个迭代器实例可能会消耗较多的内存资源。特别要注意的是:如果迭代器在遍历时,修改了集合的内容,可能会引发一致性问题。

相关推荐
熊出没15 小时前
AI Agent 生产级架构设计:工具调用与MCP、RAG、安全护栏与评估体系
人工智能·安全·架构设计·rag·ai agent
2302_8057710717 小时前
Day23:POM设计模式(封装)
java·开发语言·设计模式
熊出没17 小时前
高并发高可用系统设计:限流、熔断、降级与多级缓存实战指南
缓存·高并发·架构设计·高可用
考研保研资料分享18 小时前
北大软微软件工程保研经验:哈工大面试追问与补录风险取舍
面试·职场和发展·软件工程
风123456789~18 小时前
【架构设计】习题1 数据库题目 2/2
架构设计·数据库习题
熊出没19 小时前
从Copilot到Agent:AI编程工作流的重构与落地实践
软件工程·copilot·ai编程·ai agent
rolt19 小时前
向窃·格瓦拉学习本体建模基础知识-第7赛季第22轮
软件工程·uml·面向对象·数理逻辑
loulanyue_20 小时前
蚂蚁集团AI Native研发实践:从面向人类到面向Agent的下一代技术架构演进——读平台技术事业群总架构师黄挺2026云栖专场演讲
人工智能·大模型·软件架构·蚂蚁集团
iCxhust2 天前
先想好如何测试:让测试驱动你的程序设计
运维·服务器·软件工程·编程的艺术
对讲机数码科普2 天前
从电路原理到协议栈:模拟对讲机与数字对讲机的技术内核拆解
运维·网络·架构·软件工程