【设计模式】迭代器模式(迭代子模式):遍历集合无忧,灵活性满满,支持多种遍历方式,应对不同需求,集合遍历神器,轻松应对复杂场景,优雅遍历,提升代码质量

发布时间:2024年01月20日

前言:

迭代子模式(迭代器模式)是一种行为设计模式,它允许你在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中的所有元素。

在迭代子模式中,迭代子(Iterator)是一个对象,它知道如何遍历集合,并且跟踪它当前在集合中的位置。迭代子提供了一种统一的方式来访问集合中的元素,无论集合的具体类型如何。

迭代子模式的核心思想是将遍历集合的行为从集合本身中分离出来,这样集合就可以专注于自己的数据结构,而遍历集合的逻辑可以被封装在迭代子中。这种分离使得集合和遍历逻辑可以相互独立地变化,而不会相互影响。

迭代子模式在实际开发中经常用于处理集合类的数据,例如数组、列表、树等。它提供了一种灵活的方式来遍历集合中的元素,同时也使得代码更易于维护和扩展。

总的来说,迭代子模式通过将遍历集合的逻辑抽象出来,使得集合和遍历逻辑可以相互独立地变化,从而提高了代码的灵活性和可维护性。

一、原理及示例代码

迭代子模式的原理是将集合的遍历行为抽象为一个迭代子对象,这个迭代子对象知道如何遍历集合并跟踪当前位置。通过迭代子模式,我们可以在不暴露集合内部结构的情况下,访问集合中的元素。

下面是一个简单的 C++ 示例代码,演示了如何使用迭代子模式来遍历一个集合:

#include <iostream>
#include <vector>

// 迭代子接口
template <class T>
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual T next() = 0;
};

// 具体的迭代子实现
template <class T>
class ConcreteIterator : public Iterator<T> {
private:
    std::vector<T> collection;
    int index;

public:
    ConcreteIterator(std::vector<T> coll) : collection(coll), index(0) {}

    bool hasNext() {
        return index < collection.size();
    }

    T next() {
        return collection[index++];
    }
};

// 集合类
template <class T>
class Aggregate {
public:
    virtual Iterator<T>* createIterator() = 0;
};

// 具体的集合类
template <class T>
class ConcreteAggregate : public Aggregate<T> {
private:
    std::vector<T> collection;

public:
    void add(T item) {
        collection.push_back(item);
    }

    Iterator<T>* createIterator() {
        return new ConcreteIterator<T>(collection);
    }
};

int main() {
    ConcreteAggregate<int> aggregate;
    aggregate.add(1);
    aggregate.add(2);
    aggregate.add(3);

    Iterator<int>* iterator = aggregate.createIterator();
    while (iterator->hasNext()) {
        std::cout << iterator->next() << " ";
    }
    delete iterator;

    return 0;
}

在这个示例中,我们定义了一个抽象的迭代子接口?Iterator,以及一个具体的迭代子实现?ConcreteIterator。我们还定义了一个抽象的集合类?Aggregate,以及一个具体的集合类?ConcreteAggregate。在?ConcreteAggregate?类中,我们实现了?createIterator?方法来创建迭代子对象。

在?main?函数中,我们创建了一个?ConcreteAggregate?对象,并向其中添加了一些元素。然后我们通过调用?createIterator?方法来获取迭代子对象,并使用迭代子对象来遍历集合中的元素。

这个示例演示了迭代子模式的基本原理,即通过将集合的遍历行为抽象为一个迭代子对象,从而实现了集合的遍历和访问。

二、结构图

迭代子模式的结构图包括以下几个关键组件:

  1. 迭代子接口(Iterator):定义了遍历集合元素的接口,包括?hasNext?方法用于检查是否还有下一个元素,以及?next?方法用于获取下一个元素的值。

  2. 具体迭代子(ConcreteIterator):实现了迭代子接口,负责实际的遍历集合操作。

  3. 集合接口(Aggregate):定义了创建迭代子对象的接口方法。

  4. 具体集合类(ConcreteAggregate):实现了集合接口,负责创建具体的迭代子对象。

下面是迭代子模式的结构图示例:

-----------------------------------------
|               Aggregate               |
-----------------------------------------
| + createIterator(): Iterator          |
-----------------------------------------
                    ^
                    |
                    |
-----------------------------------------
|              Iterator                 |
-----------------------------------------
| + hasNext(): bool                      |
| + next(): Element                     |
-----------------------------------------
                    ^
                    |
                    |
-----------------------------------------
|        ConcreteAggregate              |
-----------------------------------------
| + createIterator(): Iterator          |
-----------------------------------------
                    ^
                    |
                    |
-----------------------------------------
|       ConcreteIterator               |
-----------------------------------------
| - collection: Collection              |
| - index: int                          |
-----------------------------------------
| + hasNext(): bool                     |
| + next(): Element                    |
-----------------------------------------

在这个结构图中,集合类(ConcreteAggregate)通过?createIterator?方法来创建具体的迭代子对象(ConcreteIterator)。迭代子对象实现了?Iterator?接口,包括?hasNext?和?next?方法,用于遍历集合中的元素。

通过迭代子模式,集合类和遍历逻辑得以分离,使得集合类可以专注于自身的数据结构,而遍历集合的逻辑则被封装在迭代子中。这种分离提高了代码的灵活性和可维护性,同时也使得不同类型的集合可以共享相同的遍历逻辑。

三、使用场景

迭代子模式通常在以下情况下使用:

  1. 需要遍历集合对象:当需要遍历集合对象中的元素,并且希望将遍历逻辑与集合对象本身分离时,可以使用迭代子模式。迭代子模式将集合的遍历行为抽象为一个迭代子对象,使得集合可以专注于自身的数据结构,而遍历逻辑则被封装在迭代子中。

  2. 需要支持多种遍历方式:迭代子模式可以支持多种不同的遍历方式,例如顺序遍历、逆序遍历、跳跃遍历等。通过使用不同的迭代子对象,可以实现不同的遍历方式,而无需修改集合对象本身。

  3. 需要遍历不同类型的集合:迭代子模式可以使得不同类型的集合共享相同的遍历逻辑。通过定义统一的迭代子接口,不同类型的集合可以返回相应的迭代子对象,从而实现统一的遍历方式。

  4. 需要对遍历逻辑进行封装:迭代子模式可以将遍历逻辑封装在迭代子对象中,使得集合对象可以简化遍历操作的实现。同时,迭代子模式也可以隐藏集合的内部结构,提供更加简洁的接口供外部使用。

综上所述,迭代子模式适用于需要遍历集合对象并且希望将遍历逻辑与集合对象分离的情况,以及需要支持多种遍历方式或对遍历逻辑进行封装的场景。

具体使用场景:

假设我们有一个需求,需要对不同类型的集合进行遍历,并且希望能够支持多种不同的遍历方式。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来实现对不同类型的集合进行遍历:

#include <iostream>
#include <vector>
#include <list>

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual int next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<int> collection;
    int index;

public:
    ConcreteIterator(std::vector<int> coll) : collection(coll), index(0) {}

    bool hasNext() override {
        return index < collection.size();
    }

    int next() override {
        return collection[index++];
    }
};

// 集合接口
class Aggregate {
public:
    virtual Iterator* createIterator() = 0;
};

// 具体集合类
class ConcreteAggregate : public Aggregate {
private:
    std::vector<int> collection;

public:
    ConcreteAggregate(std::vector<int> coll) : collection(coll) {}

    Iterator* createIterator() override {
        return new ConcreteIterator(collection);
    }
};

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    ConcreteAggregate agg(vec);
    Iterator* iter = agg.createIterator();

    while (iter->hasNext()) {
        std::cout << iter->next() << " ";
    }
    std::cout << std::endl;

    delete iter;

    std::list<int> lst = {6, 7, 8, 9, 10};
    ConcreteAggregate agg2(std::vector<int>(lst.begin(), lst.end()));
    Iterator* iter2 = agg2.createIterator();

    while (iter2->hasNext()) {
        std::cout << iter2->next() << " ";
    }
    std::cout << std::endl;

    delete iter2;

    return 0;
}

在这个示例中,我们定义了一个迭代子接口?Iterator,以及一个具体的迭代子实现?ConcreteIterator。同时,我们定义了一个集合接口?Aggregate,以及一个具体的集合类?ConcreteAggregate。在?main?函数中,我们使用迭代子模式来遍历了两个不同类型的集合:一个是?std::vector,另一个是?std::list。通过迭代子模式,我们可以使用统一的方式来遍历不同类型的集合,并且可以方便地支持多种不同的遍历方式。

假设我们有一个需求,需要对一个复杂的数据结构进行遍历,并且希望能够隐藏数据结构的内部实现,提供简洁的遍历接口给外部使用。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来对一个复杂的数据结构进行遍历:

#include <iostream>
#include <vector>

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual int next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<int> collection;
    size_t index;

public:
    ConcreteIterator(std::vector<int> coll) : collection(coll), index(0) {}

    bool hasNext() override {
        return index < collection.size();
    }

    int next() override {
        return collection[index++];
    }
};

// 复杂数据结构
class ComplexDataStructure {
private:
    std::vector<int> data;

public:
    void addData(int value) {
        data.push_back(value);
    }

    Iterator* createIterator() {
        return new ConcreteIterator(data);
    }
};

int main() {
    ComplexDataStructure complexData;
    complexData.addData(1);
    complexData.addData(2);
    complexData.addData(3);

    Iterator* iter = complexData.createIterator();

    while (iter->hasNext()) {
        std::cout << iter->next() << " ";
    }
    std::cout << std::endl;

    delete iter;

    return 0;
}

在这个示例中,我们定义了一个复杂的数据结构?ComplexDataStructure,并且提供了一个?createIterator?方法来创建迭代子对象。通过迭代子模式,我们可以隐藏?ComplexDataStructure?的内部实现细节,提供一个简洁的遍历接口给外部使用。外部代码可以通过迭代子对象来遍历复杂的数据结构,而无需了解数据结构的具体实现。

这个示例展示了迭代子模式在隐藏数据结构内部实现、提供简洁的遍历接口方面的应用场景。

假设我们有一个需求,需要对一个文件夹中的文件进行遍历,并且希望能够提供不同的遍历方式,比如深度优先遍历、广度优先遍历等。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来对文件夹中的文件进行遍历:

#include <iostream>
#include <string>
#include <vector>

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual std::string next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<std::string> fileList;
    size_t index;

public:
    ConcreteIterator(std::vector<std::string> files) : fileList(files), index(0) {}

    bool hasNext() override {
        return index < fileList.size();
    }

    std::string next() override {
        return fileList[index++];
    }
};

// 文件夹类
class Folder {
private:
    std::vector<std::string> files;

public:
    void addFile(const std::string& fileName) {
        files.push_back(fileName);
    }

    Iterator* createIterator() {
        return new ConcreteIterator(files);
    }
};

int main() {
    Folder folder;
    folder.addFile("file1.txt");
    folder.addFile("file2.txt");
    folder.addFile("file3.txt");

    Iterator* iter = folder.createIterator();

    while (iter->hasNext()) {
        std::cout << iter->next() << std::endl;
    }

    delete iter;

    return 0;
}

在这个示例中,我们定义了一个文件夹类?Folder,并且提供了一个?createIterator?方法来创建迭代子对象。通过迭代子模式,我们可以方便地对文件夹中的文件进行遍历,并且可以方便地支持不同的遍历方式,比如深度优先遍历、广度优先遍历等。外部代码可以通过迭代子对象来遍历文件夹中的文件,而无需了解文件夹的具体实现。

这个示例展示了迭代子模式在对文件夹中的文件进行遍历、支持不同的遍历方式方面的应用场景。

假设我们有一个需求,需要对一个公司的员工列表进行遍历,并且希望能够提供不同的遍历方式,比如按照部门遍历、按照职位级别遍历等。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来对公司的员工列表进行遍历:

#include <iostream>
#include <string>
#include <vector>

// 员工类
class Employee {
private:
    std::string name;
    std::string department;
    int level;

public:
    Employee(const std::string& n, const std::string& dep, int lvl) : name(n), department(dep), level(lvl) {}

    std::string getName() const {
        return name;
    }

    std::string getDepartment() const {
        return department;
    }

    int getLevel() const {
        return level;
    }
};

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual Employee* next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<Employee*> employeeList;
    size_t index;

public:
    ConcreteIterator(std::vector<Employee*> employees) : employeeList(employees), index(0) {}

    bool hasNext() override {
        return index < employeeList.size();
    }

    Employee* next() override {
        return employeeList[index++];
    }
};

// 公司类
class Company {
private:
    std::vector<Employee*> employees;

public:
    void addEmployee(Employee* emp) {
        employees.push_back(emp);
    }

    Iterator* createIterator() {
        return new ConcreteIterator(employees);
    }
};

int main() {
    Company company;
    company.addEmployee(new Employee("Alice", "HR", 2));
    company.addEmployee(new Employee("Bob", "Engineering", 3));
    company.addEmployee(new Employee("Charlie", "Sales", 1));

    Iterator* iter = company.createIterator();

    while (iter->hasNext()) {
        Employee* emp = iter->next();
        std::cout << "Name: " << emp->getName() << ", Department: " << emp->getDepartment() << ", Level: " << emp->getLevel() << std::endl;
    }

    delete iter;

    return 0;
}

在这个示例中,我们定义了一个公司类?Company,并且提供了一个?createIterator?方法来创建迭代子对象。通过迭代子模式,我们可以方便地对公司的员工列表进行遍历,并且可以方便地支持不同的遍历方式,比如按照部门遍历、按照职位级别遍历等。外部代码可以通过迭代子对象来遍历公司的员工列表,而无需了解公司类的具体实现。

这个示例展示了迭代子模式在对公司的员工列表进行遍历、支持不同的遍历方式方面的应用场景。

假设我们有一个需求,需要对一个网站上的文章列表进行遍历,并且希望能够提供不同的遍历方式,比如按照发布时间遍历、按照阅读量遍历等。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来对文章列表进行遍历:

#include <iostream>
#include <string>
#include <vector>

// 文章类
class Article {
private:
    std::string title;
    std::string content;
    int publishTime;
    int readCount;

public:
    Article(const std::string& t, const std::string& c, int pt, int rc) : title(t), content(c), publishTime(pt), readCount(rc) {}

    std::string getTitle() const {
        return title;
    }

    std::string getContent() const {
        return content;
    }

    int getPublishTime() const {
        return publishTime;
    }

    int getReadCount() const {
        return readCount;
    }
};

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual Article* next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<Article*> articleList;
    size_t index;

public:
    ConcreteIterator(std::vector<Article*> articles) : articleList(articles), index(0) {}

    bool hasNext() override {
        return index < articleList.size();
    }

    Article* next() override {
        return articleList[index++];
    }
};

// 文章列表类
class ArticleList {
private:
    std::vector<Article*> articles;

public:
    void addArticle(Article* article) {
        articles.push_back(article);
    }

    Iterator* createIterator() {
        return new ConcreteIterator(articles);
    }
};

int main() {
    ArticleList articleList;
    articleList.addArticle(new Article("Introduction to Design Patterns", "This article introduces design patterns.", 20220101, 1000));
    articleList.addArticle(new Article("Iterator Pattern in Depth", "This article explains the iterator pattern in depth.", 20220102, 1500));
    articleList.addArticle(new Article("Understanding Observer Pattern", "This article provides insights into the observer pattern.", 20220103, 1200));

    Iterator* iter = articleList.createIterator();

    while (iter->hasNext()) {
        Article* article = iter->next();
        std::cout << "Title: " << article->getTitle() << ", Publish Time: " << article->getPublishTime() << ", Read Count: " << article->getReadCount() << std::endl;
    }

    delete iter;

    return 0;
}

在这个示例中,我们定义了一个文章列表类?ArticleList,并且提供了一个?createIterator?方法来创建迭代子对象。通过迭代子模式,我们可以方便地对文章列表进行遍历,并且可以方便地支持不同的遍历方式,比如按照发布时间遍历、按照阅读量遍历等。外部代码可以通过迭代子对象来遍历文章列表,而无需了解文章列表类的具体实现。

这个示例展示了迭代子模式在对文章列表进行遍历、支持不同的遍历方式方面的应用场景。

假设我们有一个需求,需要对一个音乐播放列表进行遍历,并且希望能够提供不同的遍历方式,比如按照歌曲名称遍历、按照歌手名遍历等。我们可以使用迭代子模式来实现这个需求。

下面是一个简单的 C++ 代码示例,演示了如何使用迭代子模式来对音乐播放列表进行遍历:

#include <iostream>
#include <string>
#include <vector>

// 歌曲类
class Song {
private:
    std::string name;
    std::string artist;

public:
    Song(const std::string& n, const std::string& a) : name(n), artist(a) {}

    std::string getName() const {
        return name;
    }

    std::string getArtist() const {
        return artist;
    }
};

// 迭代子接口
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual Song* next() = 0;
};

// 具体迭代子实现
class ConcreteIterator : public Iterator {
private:
    std::vector<Song*> songList;
    size_t index;

public:
    ConcreteIterator(std::vector<Song*> songs) : songList(songs), index(0) {}

    bool hasNext() override {
        return index < songList.size();
    }

    Song* next() override {
        return songList[index++];
    }
};

// 音乐播放列表类
class Playlist {
private:
    std::vector<Song*> songs;

public:
    void addSong(Song* song) {
        songs.push_back(song);
    }

    Iterator* createIterator() {
        return new ConcreteIterator(songs);
    }
};

int main() {
    Playlist playlist;
    playlist.addSong(new Song("Shape of You", "Ed Sheeran"));
    playlist.addSong(new Song("Dance Monkey", "Tones and I"));
    playlist.addSong(new Song("Blinding Lights", "The Weeknd"));

    Iterator* iter = playlist.createIterator();

    while (iter->hasNext()) {
        Song* song = iter->next();
        std::cout << "Song: " << song->getName() << " - Artist: " << song->getArtist() << std::endl;
    }

    delete iter;

    return 0;
}

在这个示例中,我们定义了一个音乐播放列表类?Playlist,并且提供了一个?createIterator?方法来创建迭代子对象。通过迭代子模式,我们可以方便地对音乐播放列表进行遍历,并且可以方便地支持不同的遍历方式,比如按照歌曲名称遍历、按照歌手名遍历等。外部代码可以通过迭代子对象来遍历音乐播放列表,而无需了解音乐播放列表类的具体实现。

这个示例展示了迭代子模式在对音乐播放列表进行遍历、支持不同的遍历方式方面的应用场景。

四、优缺点

迭代子模式是一种常用的设计模式,它有以下优点和缺点:

优点:

  1. 分离了集合对象的遍历行为,使得集合对象的结构和遍历行为可以独立地变化。
  2. 简化了集合对象的接口,客户端可以通过迭代子对象来访问集合元素,而无需了解集合对象的内部结构和遍历算法。
  3. 支持多种遍历方式,客户端可以根据需要选择不同的迭代子对象来进行遍历,而无需修改集合对象的代码。

缺点:

  1. 在某些情况下,迭代子模式可能会导致性能损失,因为需要额外的对象来维护迭代状态。
  2. 迭代子模式可能会增加代码的复杂性,特别是在需要支持多种遍历方式或者需要自定义迭代子的情况下。

总体来说,迭代子模式适用于需要对集合对象进行遍历,并且希望将遍历行为和集合对象的结构分离的情况。在实际应用中,可以根据具体的需求和情况来权衡使用迭代子模式的利弊。

五、常见面试题

  1. 什么是迭代子模式?

    答案解析:迭代子模式是一种行为型设计模式,它允许客户端通过迭代子对象逐个访问集合对象的元素,而无需暴露集合对象的内部表示。这种方式可以将集合对象的遍历行为与集合对象本身分离,使得客户端可以独立地对集合对象进行遍历。
  2. 迭代子模式的主要角色有哪些?

    答案解析:迭代子模式包括以下主要角色:
    • 具体聚合对象(ConcreteAggregate):实现了聚合对象接口,负责创建具体的迭代子对象。
    • 聚合对象(Aggregate):定义了创建迭代子对象的接口。
    • 具体迭代子(ConcreteIterator):实现了抽象迭代子接口,负责对具体的集合对象进行遍历。
    • 抽象迭代子(Iterator):定义了遍历集合对象元素的接口。
  3. 迭代子模式和传统的遍历方式有何区别?

    答案解析:迭代子模式将遍历行为封装在迭代子对象中,客户端通过迭代子对象进行遍历,而不需要直接操作集合对象。传统的遍历方式通常需要直接访问集合对象的内部结构,而迭代子模式可以将遍历行为和集合对象的结构分离,提高了灵活性和封装性。
  4. 请举例说明迭代子模式的应用场景。

    答案解析:迭代子模式适用于需要对集合对象进行遍历,而且希望将遍历行为和集合对象的结构分离的情况。例如,在音乐播放列表、文件系统目录、数据库查询结果等场景中,迭代子模式可以方便地对集合对象进行遍历。
  5. 迭代子模式和访问者模式有何区别?

    答案解析:迭代子模式和访问者模式都属于行为型设计模式,但它们的目的和应用场景不同。迭代子模式用于对集合对象进行遍历,而访问者模式用于对数据结构中的元素进行操作。迭代子模式强调对集合对象的遍历行为进行封装,而访问者模式强调对数据结构中元素的操作进行封装。
  6. 迭代子模式如何支持多种遍历方式?

    答案解析:迭代子模式可以通过定义不同的具体迭代子对象来支持多种遍历方式。客户端可以根据需要选择不同的迭代子对象进行遍历,例如按照顺序遍历、倒序遍历等。
  7. 迭代子模式的优点有哪些?

    答案解析:迭代子模式的优点包括:分离了集合对象的遍历行为,简化了集合对象的接口,支持多种遍历方式,提高了代码的灵活性和可维护性。
  8. 迭代子模式的缺点有哪些?

    答案解析:迭代子模式的缺点可能包括:在某些情况下可能会导致性能损失,增加了额外的对象来维护迭代状态,可能会增加代码的复杂性。
  9. 迭代子模式和代理模式有何联系?

    答案解析:迭代子模式和代理模式都属于对象结构型设计模式,但它们的目的和应用场景不同。迭代子模式用于对集合对象进行遍历,而代理模式用于控制对对象的访问。在一些情况下,代理对象可能会实现迭代子接口,从而实现对被代理对象的遍历。
  10. 迭代子模式在实际项目中的应用举例是什么?

    答案解析:可以举例说明在实际项目中如何使用迭代子模式,例如在开发音乐播放器、文件管理系统、数据库查询接口等方面的应用。

以上问题和答案可以帮助面试者更好地理解迭代子模式的概念、作用和应用,展示其对设计模式的理解和应用能力。

?

有更多想法,可在评论区留言,也可以到下面的qun聊,👇👇👇

文章来源:https://blog.csdn.net/mars1199/article/details/135708164
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。