设计模式之迭代器模式

迭代器模式(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;
}
相关推荐
逆境不可逃14 小时前
【从零入门23种设计模式18】行为型之备忘录模式
服务器·数据库·设计模式·oracle·职场和发展·迭代器模式·备忘录模式
Real-Staok15 小时前
(集合)C / C++ 设计模式综合
单例模式·设计模式·代理模式
sg_knight17 小时前
设计模式实战:代理模式(Proxy)
python·设计模式·代理模式·proxy
Anurmy17 小时前
设计模式之命令模式
设计模式·命令模式
五点六六六1 天前
基于 AST 与 Proxy沙箱 的局部代码热验证
前端·设计模式·架构
wwdoffice01102 天前
304和316不锈钢有什么区别?哪个更好?
设计模式
网小鱼的学习笔记2 天前
创建型设计模式(工厂、builder、原型、单例)
java·后端·设计模式
逆境不可逃2 天前
【从零入门23种设计模式21】行为型之空对象模式
java·开发语言·数据库·算法·设计模式·职场和发展
蜜獾云2 天前
设计模式之命令模式:给其他模块下达命令
设计模式·命令模式
小湘西2 天前
拓扑排序(Topological Sort)
python·设计模式