迭代器模式(Iterator Pattern)是一种行为设计模式,它允许你顺序访问集合对象的元素,而无需暴露其内部表示。迭代器模式提供了一种方法,能够顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
主要特点
- 访问集合的元素而不暴露其内部结构:迭代器提供一种方法来访问集合对象中的元素,而不需要暴露其底层实现细节。
- 统一的遍历接口:无论集合的具体实现是什么,迭代器模式都提供了一个统一的接口来遍历集合中的元素。
- 多种遍历方式:通过使用不同的迭代器,能够以不同的方式遍历同一个集合对象。
示例
假设我们要实现一个可以存储各种类型的书籍的书架,并且需要一种方法来遍历这些书籍。我们可以使用迭代器模式来实现这一需求。
#include <iostream>
#include <vector>
#include <memory>
// 书类
class Book {
public:
Book(const std::string& name) : name(name) {}
std::string getName() const { return name; }
private:
std::string name;
};
// 迭代器接口
class Iterator {
public:
virtual ~Iterator() {}
virtual bool hasNext() = 0;
virtual std::shared_ptr<Book> next() = 0;
};
// 聚合接口
class Aggregate {
public:
virtual ~Aggregate() {}
virtual std::unique_ptr<Iterator> createIterator() = 0;
};
// 具体聚合类:书架
class Bookshelf : public Aggregate {
public:
void addBook(const Book& book) {
books.push_back(std::make_shared<Book>(book));
}
std::unique_ptr<Iterator> createIterator() override;
std::shared_ptr<Book> getBookAt(int index) const {
return books.at(index);
}
int getLength() const {
return books.size();
}
private:
std::vector<std::shared_ptr<Book>> books;
};
// 具体迭代器类:书架迭代器
class BookshelfIterator : public Iterator {
public:
BookshelfIterator(const Bookshelf& bookshelf)
: bookshelf(bookshelf), index(0) {}
bool hasNext() override {
return index < bookshelf.getLength();
}
std::shared_ptr<Book> next() override {
return bookshelf.getBookAt(index++);
}
private:
const Bookshelf& bookshelf;
int index;
};
std::unique_ptr<Iterator> Bookshelf::createIterator() {
return std::make_unique<BookshelfIterator>(*this);
}
// 主函数测试
int main() {
Bookshelf bookshelf;
bookshelf.addBook(Book("Design Patterns"));
bookshelf.addBook(Book("Effective C++"));
bookshelf.addBook(Book("Clean Code"));
auto iterator = bookshelf.createIterator();
while (iterator->hasNext()) {
std::shared_ptr<Book> book = iterator->next();
std::cout << book->getName() << std::endl;
}
return 0;
}
解释
-
Book:这是一个简单的类,表示一本书,包含书名。
-
Iterator:这是迭代器接口,定义了遍历元素的方法
hasNext
和next
。 -
Aggregate:这是聚合接口,定义了创建迭代器的方法
createIterator
。 -
Bookshelf:这是具体的聚合类,表示书架,包含一个存储书籍的向量。它实现了
createIterator
方法来创建一个BookshelfIterator
。 -
BookshelfIterator:这是具体的迭代器类,它实现了
Iterator
接口,用于遍历Bookshelf
中的书籍。 -
主函数:
- 创建一个
Bookshelf
对象,并向其中添加一些书籍。 - 创建一个迭代器对象,通过迭代器来遍历书架中的书籍,并输出书名。
- 创建一个
总结
迭代器模式通过提供一个统一的接口来遍历集合对象中的元素,使得客户端代码可以一致地遍历不同类型的集合对象,而不需要了解其内部实现细节。这种模式特别适用于需要访问一个聚合对象的内容而又不希望暴露其内部表示的场景。通过迭代器模式,可以更加灵活和方便地操作和遍历集合对象。
为何不直接使用vector遍历呢?
直接使用 vector
进行遍历在很多情况下确实是一个简单且有效的方法,但使用迭代器模式有其独特的优势,特别是在以下几种情况下:
1. 封装复杂数据结构
如果你的数据结构非常复杂,直接使用 vector
进行遍历可能会暴露数据结构的内部细节。迭代器模式通过封装遍历逻辑,可以隐藏这些内部细节,从而实现更好的封装性和数据抽象。
2. 统一接口
在使用迭代器模式时,所有集合对象都可以提供一个统一的接口来进行遍历。这样,客户端代码可以以相同的方式遍历不同类型的集合,而不需要了解每种集合的具体实现方式。这种一致性有助于降低客户端代码的复杂性。
3. 多种遍历方式
通过使用不同的迭代器,你可以实现多种遍历方式。例如,你可以创建一个反向迭代器来倒序遍历集合,或者创建一个筛选迭代器来只遍历满足特定条件的元素。这样的灵活性是直接使用 vector
遍历所不能轻易实现的。
4. 扩展性
当需要增加新的集合类型时,只需实现新的迭代器即可,而不需要修改现有的客户端代码。迭代器模式遵循开放-封闭原则(OCP),即对扩展开放,对修改关闭。
5. 解耦遍历算法
在迭代器模式中,遍历算法被封装在迭代器中,使得集合对象与遍历算法解耦。这使得你可以在不修改集合对象的情况下,独立地更改或扩展遍历算法。
例子
假设我们有一个复杂的树结构,而不是简单的线性集合。通过迭代器模式,可以实现对树的遍历,而不需要让客户端了解树的结构。
#include <iostream>
#include <memory>
#include <vector>
// 树节点类
class TreeNode {
public:
explicit TreeNode(int value) : value(value) {}
void addChild(std::shared_ptr<TreeNode> child) {
children.push_back(child);
}
int getValue() const {
return value;
}
const std::vector<std::shared_ptr<TreeNode>>& getChildren() const {
return children;
}
private:
int value;
std::vector<std::shared_ptr<TreeNode>> children;
};
// 迭代器接口
class Iterator {
public:
virtual ~Iterator() = default;
virtual bool hasNext() = 0;
virtual std::shared_ptr<TreeNode> next() = 0;
};
// 树的前序遍历迭代器
class PreOrderIterator : public Iterator {
public:
explicit PreOrderIterator(std::shared_ptr<TreeNode> root) {
if (root) stack.push_back(root);
}
bool hasNext() override {
return !stack.empty();
}
std::shared_ptr<TreeNode> next() override {
if (stack.empty()) return nullptr;
std::shared_ptr<TreeNode> node = stack.back();
stack.pop_back();
const auto& children = node->getChildren();
for (auto it = children.rbegin(); it != children.rend(); ++it) {
stack.push_back(*it);
}
return node;
}
private:
std::vector<std::shared_ptr<TreeNode>> stack;
};
// 主函数测试
int main() {
auto root = std::make_shared<TreeNode>(1);
auto child1 = std::make_shared<TreeNode>(2);
auto child2 = std::make_shared<TreeNode>(3);
root->addChild(child1);
root->addChild(child2);
child1->addChild(std::make_shared<TreeNode>(4));
child1->addChild(std::make_shared<TreeNode>(5));
child2->addChild(std::make_shared<TreeNode>(6));
PreOrderIterator iterator(root);
while (iterator.hasNext()) {
std::shared_ptr<TreeNode> node = iterator.next();
std::cout << node->getValue() << " ";
}
std::cout << std::endl;
return 0;
}
解释
-
TreeNode:表示树的节点,包含值和子节点列表。
-
Iterator:迭代器接口,定义了遍历树的方法
hasNext
和next
。 -
PreOrderIterator:具体的迭代器类,实现了前序遍历算法。它使用一个栈来跟踪节点的访问顺序。
-
主函数:创建一个树结构,使用前序遍历迭代器遍历树,并输出节点的值。
总结
虽然直接使用 vector
遍历在简单情况下是可行的,但在复杂数据结构和需要更多灵活性和扩展性的情况下,迭代器模式提供了更好的解决方案。通过迭代器模式,你可以实现统一的遍历接口,封装复杂数据结构,支持多种遍历方式,并且遵循开放-封闭原则,增强系统的可扩展性和可维护性。