设计模式之迭代器模式

迭代器模式(Iterator Pattern) 的核心思想就是:让用户在不知道容器内部结构的情况下,能够按顺序访问容器里的元素。

1. 场景对比:书架与书籍

假设我们有一个"书架",里面存了很多书。我们需要遍历并打印出所有书的名字。

❌ 不使用迭代器模式

在这种情况下,客户端代码必须知道书架内部是用什么存书的(比如 std::vector)。

复制代码
#include <iostream>
#include <vector>
#include <string>

class BookShelf {
public:
    std::vector<std::string> books; // 暴露了内部存储结构
    void addBook(std::string name) { books.push_back(name); }
};

int main() {
    BookShelf shelf;
    shelf.addBook("C++ Primer");
    shelf.addBook("Design Patterns");

    // 客户端必须知道用 vector 的下标或 size() 来遍历
    for (int i = 0; i < shelf.books.size(); ++i) {
        std::cout << shelf.books[i] << std::endl;
    }
    return 0;
}

痛点: 如果哪天你想把 vector 改成 list(链表)或者 set(集合),你会发现 main 函数里的遍历代码全断了,因为不同容器的访问方式不一样。


✅ 使用迭代器模式

我们给书架配一个"导航员"(迭代器),让它来处理遍历逻辑。

复制代码
#include <iostream>
#include <vector>
#include <string>

// 1. 迭代器抽象(可选,为了灵活性)
class Iterator {
public:
    virtual bool hasNext() = 0;
    virtual std::string next() = 0;
};

// 2. 具体的书架迭代器
class BookShelfIterator : public Iterator {
private:
    std::vector<std::string>& books;
    int index = 0;
public:
    BookShelfIterator(std::vector<std::string>& b) : books(b) {}
    bool hasNext() override { return index < books.size(); }
    std::string next() override { return books[index++]; }
};

// 3. 容器类
class BookShelf {
private:
    std::vector<std::string> books; // 现在是私有的了!
public:
    void addBook(std::string name) { books.push_back(name); }
    Iterator* createIterator() { return new BookShelfIterator(books); }
};

int main() {
    BookShelf shelf;
    shelf.addBook("C++ Primer");
    shelf.addBook("Design Patterns");

    // 客户端完全不需要知道底层是 vector 还是链表
    Iterator* it = shelf.createIterator();
    while (it->hasNext()) {
        std::cout << it->next() << std::endl;
    }
    
    delete it;
    return 0;
}

2. 优缺点分析

优点

  1. 封装性好:你不需要暴露容器的内部细节。哪怕以后书架改成从数据库读数据,只要迭代器接口不变,外部代码一个字都不用改。

  2. 单一职责原则:容器只负责"存东西",迭代器只负责"遍历东西"。逻辑清晰。

  3. 多态性:你可以为同一个容器提供不同的迭代器(比如:正向迭代器、反向迭代器、只读迭代器)。

缺点

  1. 开销增加:每增加一种容器,就要额外写一个迭代器类,代码量会变多。

  2. 简单场景略显复杂:如果你的数据结构非常简单(比如就是一个简单的数组),用迭代器反而觉得绕。


3. 现实中的 C++ 迭代器

其实 C++ 的标准库(STL)已经把迭代器模式玩出花来了。当你写 std::vector<int>::iterator it = v.begin(); 时,你就是在用迭代器模式。

现代 C++ (C++11及以后) 的 Range-based for loop 其实就是迭代器的语法糖:

复制代码
for (auto& book : shelf) { // 底层依然是调用 begin() 和 end() 迭代器
    std::cout << book << std::endl;
}

假设老板提出了一个新需求:"现在的书架太占内存了,请把底层存储从 vector 改成 list(双向链表),并且增加一个'只查找专业技术书'的过滤功能。"


4.需求变更:如果不使用迭代器模式

在之前的代码里,你的 main 函数(客户端)直接使用了下标遍历。因为 std::list 不支持像 vector 那样的随机访问(即不能直接用 shelf.books[i]),你的灾难就开始了:

复制代码
// ❌ 灾难现场:所有调用书架的地方都要重写
int main() {
    BookShelf shelf;
    // ... 添加书 ...

    // 报错!list 没有 [] 运算符,也没有简单的 size() 效率
    // 你必须把这段逻辑全部删掉,改成处理 list 的逻辑
    for (auto it = shelf.books.begin(); it != shelf.books.end(); ++it) { 
        if (it->find("C++") != std::string::npos) { // 还要在这里硬编码过滤逻辑
            std::cout << *it << std::endl;
        }
    }
}

后果: 只要底层改了,外部所有用到书架的业务代码全部要"手术式"修改。如果项目有 100 处在遍历书架,你就得改 100 处。


5. 需求变更:如果使用了迭代器模式

在迭代器模式下,底层容器怎么换,客户端根本感知不到

第一步:修改书架内部(客户端无感)

你只需要在 BookShelf 内部更换容器,并更新对应的 Iterator 实现即可。

第二步:增加"技术书过滤器"迭代器

你可以直接新建一个特殊的迭代器,而不需要修改原有的容器代码。这符合开闭原则(对扩展开放,对修改关闭)。

复制代码
// 新增:专门过滤技术书的迭代器
class TechBookIterator : public Iterator {
private:
    std::vector<std::string>& books;
    int index = 0;
public:
    TechBookIterator(std::vector<std::string>& b) : books(b) {}
    
    bool hasNext() override {
        // 自动跳过非技术书,只找带 "C++" 或 "Design" 字样的
        while (index < books.size()) {
            if (books[index].find("C++") != std::string::npos || 
                books[index].find("Design") != std::string::npos) {
                return true;
            }
            index++;
        }
        return false;
    }

    std::string next() override { return books[index++]; }
};
第三步:客户端的使用对比

客户端只需要换个"导航员"即可,遍历的逻辑(while 循环)一行都不用动:

复制代码
int main() {
    BookShelf shelf;
    shelf.addBook("C++ Primer");
    shelf.addBook("Cooking Guide"); // 生活类,会被过滤
    shelf.addBook("Design Patterns");

    // 哪怕换成了 TechBookIterator,下面的循环代码完全不需要改!
    Iterator* it = shelf.createTechIterator(); 
    
    while (it->hasNext()) {
        // 这里的逻辑依然保持纯粹:只管拿,不管怎么选
        std::cout << "Found Tech Book: " << it->next() << std::endl;
    }

    delete it;
    return 0;
}
相关推荐
qq_401700412 小时前
嵌入式C语言设计模式
c语言·开发语言·设计模式
SuperEugene2 小时前
常见设计模式在 JS 里的轻量用法:单例、发布订阅、策略
前端·javascript·设计模式·面试
小米4962 小时前
Js设计模式---策略模式
设计模式·策略模式
geovindu2 小时前
python: Strategy Pattern
python·设计模式·策略模式
sg_knight19 小时前
适配器模式(Adapter)
python·设计模式·适配器模式·adapter
郝学胜-神的一滴1 天前
Effective Modern C++ 条款40:深入理解 Atomic 与 Volatile 的多线程语义
开发语言·c++·学习·算法·设计模式·架构
九狼1 天前
Riverpod 2.0 代码生成与依赖注入
flutter·设计模式·github
geovindu1 天前
python: Visitor Pattern
python·设计模式·访问者模式
五阿哥永琪1 天前
常见设计模式简介
设计模式