设计模式之迭代器模式

迭代器模式(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;
}
相关推荐
楼田莉子27 分钟前
设计模式:设计模式的相关概念与原则
c++·学习·设计模式
妙蛙种子31121 小时前
【Java设计模式 | 创建者模式】工厂方法模式
java·后端·设计模式·工厂方法模式
wwdoffice01101 天前
薄 膜 干 涉
设计模式
无籽西瓜a1 天前
【西瓜带你学设计模式 | 第十二期 - 装饰器模式】装饰器模式 —— 动态叠加功能实现、优缺点与适用场景
java·后端·设计模式·软件工程·装饰器模式
无籽西瓜a1 天前
【西瓜带你学设计模式 | 第十三期 - 组合模式】组合模式 —— 树形结构统一处理实现、优缺点与适用场景
java·后端·设计模式·组合模式·软件工程
Rsun045512 天前
设计模式应该怎么学
java·开发语言·设计模式
_MyFavorite_2 天前
JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)
java·开发语言·设计模式
_MyFavorite_2 天前
JAVA重点基础、进阶知识及易错点总结(32)设计模式(建造者、原型)
java·python·设计模式
妙蛙种子3112 天前
【Java设计模式 | 创建者模式】单例模式
java·开发语言·后端·单例模式·设计模式
武藤一雄3 天前
C# 异步回调与等待机制
前端·microsoft·设计模式·微软·c#·.netcore