行为型模式——迭代器(Iterator)
问题背景
当需要重复使用某种规则遍历一个集合时,考虑使用迭代器。现有一整数集合,要求实现三个函数:
- 将其中所有负数打印出来。
- 将其中所有7的倍数打印出来。
- 将其中所有2的幂打印出来。
要实现这些功能,必须遍历这个集合。针对这个集合的类型不同,我们会使用不同的遍历规则。如果它是列表,可以用循环简单遍历一下;如果它是树,就可能用到递归。
我们发现,对于简单的集合,每次都手写遍历逻辑还无伤大雅。一旦集合变得很复杂,再每次都手写,就有点吃不消了。
解决方案
考虑到用户会经常性地遍历集合,我们可以提取一个包含遍历方法的接口,同时提供默认实现。这样,用户既可以简单地复用我们的默认遍历逻辑,也可以扩展自己的遍历逻辑。
提取两个接口IEnumerable和IEnumerator,分别表示可迭代集合和迭代器。IEnumerable中包含一个GetEnumerator方法用来获取该集合的迭代器;IEnumerator中包含HasNext,Next方法用来实现遍历逻辑。在设计集合时,如果我们希望用户能够使用迭代器来遍历集合,就让集合实现IEnumerable,同时设计一个对应的迭代器类,实现IEnumerator。该集合的GetEnumerator方法返回对应的迭代器对象,用户使用这个迭代器对象对集合进行遍历。使用迭代器的程序结构如下:
上面这种迭代器称为外部迭代器,需要用户显示地推进迭代进度。还有一种内部迭代器,用户只需要向集合传递一个操作(函数指针),迭代器就会自动对集合中的每个元素执行操作。本文不实现内部迭代器,大多数语言的标准库中都有内部迭代器的实现,可以参考。比如.Net的LINQ和Java的Stream都是内部迭代器的例子。
效果
- 可以扩展迭代器以实现多种遍历逻辑。
- 简化了集合的使用。
缺陷
使用迭代器有一个麻烦的地方就是在遍历过程中修改集合容易出现错误。如果在迭代过程中对集合进行了增删操作,则可能打乱迭代进度,出现重复或丢失元素的情况。对于这种情况,可以禁止在遍历期间对集合进行增删操作,也可以完善遍历逻辑来让迭代器支持遍历期间的增删操作。微软在C#中采取的方案就是禁止遍历期间(foreach循环)的增删操作。
相关模式
- 复合:迭代器常用来定义复合结构的遍历逻辑。
- 工厂方法:如果一个集合的迭代器是多态的,可以用工厂方法创建对应的实现。
- 备忘录:可以用来储存迭代器的状态。
实现
using System;
namespace Iterator
{
class Client
{
public interface IEnumerable
{
IEnumerator Enumerator { get; }
}
public interface IEnumerator
{
void Reset();
bool HasNext { get; }
int Next { get; }
}
public class List : IEnumerable
{
private int[] items;
public List(params int[] items)
{
this.items = items;
}
public int Length => items.Length;
public IEnumerator Enumerator
{
get
{
return new ListEnumerator(this);
}
}
public int this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
}
}
}
public class ListEnumerator : IEnumerator
{
private List target;
private int currentPos = -1;
public ListEnumerator(List target)
{
this.target = target;
}
public void Reset()
{
currentPos = -1;
}
public bool HasNext => currentPos < target.Length - 1;
public int Next => target[++currentPos];
}
static void Main(string[] args)
{
var list = new List(-2, 1, 5, 14, 0, -5, -3, -77, 128, 9, 32);
var enumerator = list.Enumerator;
int item;
Console.WriteLine("负数:");
while (enumerator.HasNext)
{
item = enumerator.Next;
if (item < 0)
{
Console.WriteLine(item);
}
}
Console.WriteLine("7的倍数:");
enumerator.Reset();
while (enumerator.HasNext)
{
item = enumerator.Next;
if (item % 7 == 0)
{
Console.WriteLine(item);
}
}
Console.WriteLine("2的幂:");
enumerator.Reset();
while (enumerator.HasNext)
{
item = enumerator.Next;
if (item > 0 && (item & item - 1) == 0)
{
Console.WriteLine(item);
}
}
}
}
}