行为型设计模式之迭代器模式
文章目录
1. 迭代器模式简介
迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。迭代器模式属于行为型模式,因为它定义了对象之间的交互方式,特别是集合与遍历算法之间的交互。
在软件开发中,我们经常需要遍历集合中的元素。如果为每一种集合都提供一种遍历方法,一方面会使得设计过于复杂,另一方面也会暴露集合内部结构。迭代器模式将遍历逻辑从集合类中分离出来,使得集合只需专注于元素的管理,而遍历操作则由迭代器类负责。
2. 迭代器模式的结构
迭代器模式主要包含以下几个角色:
- Iterator(迭代器):定义访问和遍历元素的接口,通常包含
First()
、Next()
、IsDone()
和CurrentItem()
等方法。 - ConcreteIterator(具体迭代器):实现迭代器接口,并记录遍历中的当前位置。
- Aggregate(聚合):定义创建迭代器对象的接口。
- ConcreteAggregate(具体聚合):实现聚合接口,返回一个具体迭代器的实例。
3. 迭代器模式的实现(C#)
下面使用C#代码演示迭代器模式的实现。我们将创建一个简单的图书管理系统,其中包含一个书架(BookShelf)作为聚合对象,以及相应的迭代器。
3.1 传统实现方式
using System;
using System.Collections.Generic;
// 迭代器接口
public interface IIterator
{
bool HasNext(); // 是否有下一个元素
object Next(); // 获取下一个元素
}
// 聚合接口
public interface IAggregate
{
IIterator CreateIterator(); // 创建迭代器
}
// 书类,表示书架上的一本书
public class Book
{
private string name; // 书名
public Book(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
}
// 具体聚合类:书架
public class BookShelf : IAggregate
{
private List<Book> books; // 存储书籍的集合
private int last = 0; // 当前最后一本书的索引
public BookShelf(int maxSize)
{
this.books = new List<Book>(maxSize);
}
// 添加书籍
public void AppendBook(Book book)
{
this.books.Add(book);
last++;
}
// 获取指定索引的书籍
public Book GetBookAt(int index)
{
return books[index];
}
// 获取书架中书籍数量
public int GetLength()
{
return last;
}
// 创建并返回该书架的迭代器
public IIterator CreateIterator()
{
return new BookShelfIterator(this);
}
}
// 具体迭代器类:书架迭代器
public class BookShelfIterator : IIterator
{
private BookShelf bookShelf; // 关联的书架
private int index; // 当前遍历位置
public BookShelfIterator(BookShelf bookShelf)
{
this.bookShelf = bookShelf;
this.index = 0;
}
// 检查是否还有下一个元素
public bool HasNext()
{
if (index < bookShelf.GetLength())
{
return true;
}
return false;
}
// 获取下一个元素并移动指针
public object Next()
{
Book book = bookShelf.GetBookAt(index);
index++;
return book;
}
}
// 客户端代码
public class Program
{
static void Main(string[] args)
{
// 创建书架并添加书籍
BookShelf bookShelf = new BookShelf(4);
bookShelf.AppendBook(new Book("设计模式"));
bookShelf.AppendBook(new Book("图解HTTP"));
bookShelf.AppendBook(new Book("C#高级编程"));
bookShelf.AppendBook(new Book("算法导论"));
// 获取迭代器并遍历书架中的所有书籍
IIterator it = bookShelf.CreateIterator();
while (it.HasNext())
{
Book book = (Book)it.Next();
Console.WriteLine(book.GetName());
}
}
}
3.2 使用C#内置迭代器
C#提供了内置的迭代器模式支持,通过实现IEnumerable
和IEnumerator
接口,以及使用yield return
关键字,可以更加简洁地实现迭代器模式:
using System;
using System.Collections;
using System.Collections.Generic;
// 书类
public class Book
{
public string Name { get; private set; }
public Book(string name)
{
Name = name;
}
}
// 书架类,实现IEnumerable接口
public class BookShelf : IEnumerable<Book>
{
private List<Book> books = new List<Book>();
// 添加书籍
public void AppendBook(Book book)
{
books.Add(book);
}
// 获取指定索引的书籍
public Book GetBookAt(int index)
{
return books[index];
}
// 获取书架中书籍数量
public int Count
{
get { return books.Count; }
}
// 实现IEnumerable<Book>接口的GetEnumerator方法
public IEnumerator<Book> GetEnumerator()
{
// 使用yield return简化迭代器实现
foreach (var book in books)
{
yield return book;
}
}
// 实现非泛型IEnumerable接口的GetEnumerator方法
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// 提供按照特定顺序迭代的方法
public IEnumerable<Book> GetReverseIterator()
{
for (int i = books.Count - 1; i >= 0; i--)
{
yield return books[i];
}
}
}
// 客户端代码
public class Program
{
static void Main(string[] args)
{
// 创建书架并添加书籍
BookShelf bookShelf = new BookShelf();
bookShelf.AppendBook(new Book("设计模式"));
bookShelf.AppendBook(new Book("图解HTTP"));
bookShelf.AppendBook(new Book("C#高级编程"));
bookShelf.AppendBook(new Book("算法导论"));
Console.WriteLine("正向遍历:");
// 使用foreach直接遍历书架
foreach (Book book in bookShelf)
{
Console.WriteLine(book.Name);
}
Console.WriteLine("\n反向遍历:");
// 使用自定义迭代方式
foreach (Book book in bookShelf.GetReverseIterator())
{
Console.WriteLine(book.Name);
}
}
}
3.3 更复杂的实例:多种遍历方式
下面是一个更复杂的例子,展示如何为同一个集合实现多种遍历方式:
using System;
using System.Collections;
using System.Collections.Generic;
// 课程类
public class Course
{
public string Name { get; private set; }
public string Category { get; private set; }
public Course(string name, string category)
{
Name = name;
Category = category;
}
public override string ToString()
{
return $"{Name} ({Category})";
}
}
// 课程目录类
public class CourseCatalog : IEnumerable<Course>
{
private List<Course> courses = new List<Course>();
// 添加课程
public void AddCourse(Course course)
{
courses.Add(course);
}
// 默认迭代器 - 按添加顺序遍历所有课程
public IEnumerator<Course> GetEnumerator()
{
return courses.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// 按分类迭代器 - 先返回特定分类的课程
public IEnumerable<Course> GetCoursesByCategory(string category)
{
// 首先返回指定分类的课程
foreach (var course in courses)
{
if (course.Category.Equals(category, StringComparison.OrdinalIgnoreCase))
{
yield return course;
}
}
// 然后返回其他分类的课程
foreach (var course in courses)
{
if (!course.Category.Equals(category, StringComparison.OrdinalIgnoreCase))
{
yield return course;
}
}
}
// 按名称排序的迭代器
public IEnumerable<Course> GetCoursesOrderedByName()
{
// 创建临时列表并排序
List<Course> sortedCourses = new List<Course>(courses);
sortedCourses.Sort((c1, c2) => string.Compare(c1.Name, c2.Name, StringComparison.Ordinal));
// 返回排序后的结果
foreach (var course in sortedCourses)
{
yield return course;
}
}
}
// 客户端代码
public class Program
{
static void Main(string[] args)
{
// 创建课程目录
CourseCatalog catalog = new CourseCatalog();
// 添加各种课程
catalog.AddCourse(new Course("C#基础", "编程"));
catalog.AddCourse(new Course("数据结构", "计算机科学"));
catalog.AddCourse(new Course("设计模式", "软件工程"));
catalog.AddCourse(new Course("Java进阶", "编程"));
catalog.AddCourse(new Course("Web开发", "编程"));
catalog.AddCourse(new Course("人工智能", "计算机科学"));
// 使用默认迭代器(按添加顺序)
Console.WriteLine("按添加顺序遍历课程:");
foreach (var course in catalog)
{
Console.WriteLine(course);
}
// 使用分类迭代器(先显示"编程"类课程)
Console.WriteLine("\n按分类遍历课程(优先显示编程类):");
foreach (var course in catalog.GetCoursesByCategory("编程"))
{
Console.WriteLine(course);
}
// 使用名称排序迭代器
Console.WriteLine("\n按课程名称字母顺序遍历:");
foreach (var course in catalog.GetCoursesOrderedByName())
{
Console.WriteLine(course);
}
}
}
4. 迭代器模式的优缺点
4.1 优点
- 单一职责原则:将集合的遍历行为分离到迭代器中,使集合专注于元素管理,迭代器专注于遍历算法。
- 开闭原则:可以方便地增加新的遍历方式,而无需修改集合类。
- 封装性良好:隐藏了集合的内部结构,客户端只需要使用迭代器接口。
- 支持多种遍历方式:可以为同一个集合定义多种不同的迭代方式。
- 简化客户端代码:客户端不需要关心集合的内部实现,只需要通过统一的迭代器接口遍历元素。
4.2 缺点
- 增加系统复杂度:引入额外的迭代器类,增加了系统的复杂性。
- 对简单集合可能过度设计:对于结构简单的集合,使用迭代器模式可能会显得有些繁琐。
- 多线程环境下的同步问题:在多线程环境下,如果一个线程修改了集合,而另一个线程正在迭代,可能会导致问题。
5. 迭代器模式的适用场景
- 需要访问复杂数据结构而不暴露其内部表示:如树形结构、图结构等。
- 需要提供多种遍历方式:如按层次、按深度优先、按广度优先等。
- 需要为同一集合提供多种迭代方式:如正向遍历、反向遍历、按特定条件过滤等。
- 需要统一访问不同类型集合的接口:迭代器提供了一个标准的访问集合元素的方法。
6. 迭代器模式与其他模式的关系
- 工厂方法模式:聚合对象通常使用工厂方法模式创建迭代器。
- 组合模式:迭代器常用于遍历组合模式中的复杂结构。
- 备忘录模式:迭代器可以使用备忘录模式存储迭代状态,以便恢复到特定位置。
- 访问者模式:迭代器可以与访问者模式结合,在遍历集合的同时对元素执行操作。
7. 在实际开发中的应用
7.1 .NET中的迭代器模式
.NET Framework和.NET Core中广泛应用了迭代器模式,主要通过以下接口和类实现:
- IEnumerable和IEnumerator:泛型集合迭代接口
- IEnumerable和IEnumerator:非泛型集合迭代接口
- yield return语句:简化迭代器实现
几乎所有的.NET集合类都实现了这些接口,使得它们可以被foreach语句遍历。
7.2 实际应用场景
- 集合类的遍历:List、Dictionary、HashSet等集合类的遍历。
- 数据库查询结果的迭代:使用ADO.NET或ORM框架查询数据库时,结果集通常以迭代器形式返回。
- 自定义集合类型:当实现自定义集合类型时,通过实现IEnumerable接口提供标准的遍历方式。
- 复杂数据结构的遍历:如树形结构、图结构等的遍历。
- 延迟加载和流处理:使用yield return实现延迟加载和流处理,只在需要时才计算和返回元素。
8. 迭代器模式的变体
8.1 内部迭代器与外部迭代器
- 内部迭代器:迭代逻辑由集合类内部控制,客户端传入要执行的操作(如C#中的ForEach方法)。
- 外部迭代器:迭代控制权在客户端,客户端通过调用迭代器方法控制遍历过程(如传统的迭代器模式)。
8.2 快照迭代器
在多线程环境下,当一个线程遍历集合而另一个线程可能修改集合时,可以使用快照迭代器,即在开始遍历时创建一个集合的快照,迭代器操作的是快照而不是原集合。
// 快照迭代器示例
public class SnapshotBookShelf : IEnumerable<Book>
{
private List<Book> books = new List<Book>();
public void AddBook(Book book)
{
books.Add(book);
}
public IEnumerator<Book> GetEnumerator()
{
// 创建一个快照,确保迭代过程中不受集合修改的影响
List<Book> snapshot = new List<Book>(books);
return snapshot.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
9. 总结
迭代器模式是一种强大且常用的设计模式,它使我们能够将集合的遍历逻辑与集合的实现分离,实现了单一职责原则。在C#等现代编程语言中,迭代器模式已经成为标准库的一部分,通过接口和语言特性得到了良好的支持。
理解并掌握迭代器模式,可以帮助我们编写更加灵活、可维护的代码,特别是在处理复杂数据结构和提供多种遍历方式的场景下。无论是使用语言内置的迭代器支持,还是手动实现迭代器模式,都能为我们的代码带来清晰的结构和良好的可维护性。
参考资料和学习链接
- 设计模式:可复用面向对象软件的基础
- Head First 设计模式
- C# 中的迭代器模式官方文档
- Refactoring.Guru - 迭代器模式
- C# Corner - Iterator Design Pattern