在C#或者Java语言中都提供了一些机制来支持iterator模式.最近在看《Head First Design Pattern》,有了一点自己的想法,就把它给记录了下来。
假设我们有两个包含MenuItem集合的类,一个为PancakeHouseMenu,另一个为DinerMenu。他们的定义和实现分别如下:(注:大部分代码是从书中搬过来略作修改)
MenuItem:

Code
1
using System;
2
3
public class MenuItem
4

{
5
private fields#region private fields
6
7
private string name;
8
private string description;
9
private bool vegetarian;
10
private double price;
11
12
#endregion
13
14
public properties#region public properties
15
16
public string Name
17
{
18
get
{return name;}
19
}
20
21
public string Description
22
{
23
get
{return description;}
24
}
25
26
public bool Vegetarian
27
{
28
get
{return vegetarian;}
29
}
30
31
public double Price
32
{
33
get
{return price;}
34
}
35
36
#endregion
37
38
constructor#region constructor
39
40
public MenuItem(string name,
41
string description,
42
bool vegetarian,
43
double price)
44
{
45
this.name = name;
46
this.description = description;
47
this.vegetarian = vegetarian;
48
this.price = price;
49
}
50
51
#endregion
52
}PancakeHouseMenu:

Code
1
using System;
2
using System.Collections;
3
4
public class PancakeHouseMenu
5

{
6
ArrayList menuItems;
7
8
public ArrayList MenuItems
9
{
10
get
{return menuItems;}
11
}
12
13
public PancakeHouseMenu()
14
{
15
menuItems = new ArrayList();
16
17
AddItem("Pancake 1", "eggs", true, 2.99);
18
AddItem("Pancake 2", "sausage", false, 2.99);
19
AddItem("Pancake 3", "fried", true, 3.49);
20
}
21
22
public void AddItem(string name, string description,
23
bool vegetarian, double price)
24
{
25
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
26
menuItems.Add(menuItem);
27
}
28
}DinerMenu:

Code
1
using System;
2
using System.Collections;
3
4
public class DinerMenu
5

{
6
const int MAX_ITEMS = 6;
7
int numberOfItems = 0;
8
MenuItem[] menuItems;
9
10
public MenuItem[] MenuItems
11
{
12
get
{return menuItems;}
13
}
14
15
public DinerMenu()
16
{
17
menuItems = new MenuItem[MAX_ITEMS];
18
19
AddItem()
20
}
21
22
public void AddItem(string name, string description,
23
bool vegetarian, double price)
24
{
25
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
26
if(numberOfItems >= MAX_ITEMS)
27
{
28
Conosle.WriteLine("Sorry, menu is full!");
29
}
30
else
31
{
32
menuItems[numberOfItems] = menuItem;
33
numberOfItems++;
34
}
35
}
36
}上面的代码相对比较简单,每个Menu类所做的事情就是提供一些基本的访问方法和属性。
现在,如果我们要加一个Waitress类来打印每个Menu里的项的话,可能就需要写成这样了。

Code
1
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
2
ArrayList breakfastItems = pancakeHouseMenu.MenuItems;
3
4
DinerMenu dinerMenu = new DinerMenu();
5
MenuItem[] lunchItems = dinerMenu.MenuItems;
6
7
//print pancakeHouseMenu
8
for(int i = 0; i < breakfastItems.Count; i ++)
9

{
10
MenuItem menuItem = (MenuItem)breakfastItems[i];
11
Console.WriteLine(menuItem.Name);
12
Console.WriteLine(menuItem.Price);
13
Console.WriteLine(menuItem.Description);
14
}
15
16
// print dinerMenu
17
for(int i = 0; i < lunchItems.Length; i++)
18

{
19
MenuItem menuItem = (MenuItem)breakfastItems[i];
20
Console.WriteLine(menuItem.Name);
21
Console.WriteLine(menuItem.Price);
22
Console.WriteLine(menuItem.Description);
23
}可以看到为了遍历每个集合,我们需要针对每个具体的集合实现使用一个for循环。这下子问题就来了,如果以后又有新的menu类来了呢?难道每次都往Waitress类里面加?这样子下下去肯定不行。
再仔细想想前面的代码,很重要的一部分在于我们是针对每个集合的具体实现来做循环的,也就是说集合里面的具体实现都暴露在客户端面前。而且,每次都要写一个循环,循环中的很多代码又是重复的,这可是一个很不好的设计。我们所期望的方式肯定是最好用一个统一的循环便利方式来访问每个集合类.比如说:
while(iterator.hasNext())
{
MenuItem item = (MenuItem)iterator.next();
//....
}
这样每次针对具体的集合只要调一个遍历的方法就可以了。
如果我们要达到这个目的,那么我们是不是可以提供一个接口定义hasNext和next等方法,然后由具体的类来实现,而在客户端只要针对这个接口进行变成就好了?
我们不妨定义一组如下的类:

这样在Waitress的PrintMenu方法可以引用Iterator接口作为参数,也分离了对实现的耦合。另外,我们定义的PancakeMenu和DinerMenu和Iterator之间又是什么关系呢?如何把他们关联起来?
其实,我们前面的代码还有一个问题就是每个类中间都有一个集合,这个类不但实现了自身的业务职能,还有一个遍历每个元素的职能。从OO的角度来说违背了单一职责的原则。如果我们把遍历每个元素的职责给剥离出来就可以保证了。所以说,每个Iterator的子类应该就是针对每个Menu类的遍历部分的具体实现,这样Menu类只要把自己的具体集合传给对应的Iterator子类,然后由他们去实现遍历,自己不用管。
这样我们整个实现的类图如下:

我们再回头看看这个设计,Waitress直接依赖于DinerMenu,PancakeMenu.对具体类的依赖似乎有悖于OO的原则,如果我们定义一个接口,让Waitress依赖于一个公用的接口会怎么样呢?回头看看两个Menu类,实际上都包含一个公有的部分,就是创建Iterator的方法。如此一来,我们可以将设计修改成如下:

下面是针对每个类的具体实现代码:
Menu:

Code
1
using System;
2
3
namespace HeadFirstDesignPatterns.Iterator.Menu
4

{
5
/**//// <summary>
6
/// Summary description for Menu.
7
/// </summary>
8
public interface Menu
9
{
10
Iterator CreateIterator();
11
}
12
}
13
Iterator:

Code
1
using System;
2
3
namespace HeadFirstDesignPatterns.Iterator.Menu
4

{
5
/**//// <summary>
6
/// Summary description for Iterator.
7
/// </summary>
8
public interface Iterator
9
{
10
bool HasNext();
11
object Next();
12
}
13
}
14
DinerMenu:

Code
1
using System;
2
3
namespace HeadFirstDesignPatterns.Iterator.Menu
4

{
5
/**//// <summary>
6
/// Summary description for DinnerMenu.
7
/// </summary>
8
public class DinnerMenu : Menu
9
{
10
static int MAX_ITEMS = 6;
11
int numberOfItems = 0;
12
MenuItem[] menuItems;
13
14
public DinnerMenu()
15
{
16
menuItems = new MenuItem[MAX_ITEMS];
17
18
AddItem("Vegetarian BLT",
19
"(Fakin') Bacon with lettuce & tomato on whole wheat",
20
true,
21
2.99);
22
AddItem("BLT",
23
"Bacon with lettuce & tomato on whole wheat",
24
false,
25
2.99);
26
AddItem("Soup of the day",
27
"Soup of the day, with a side of potato salad",
28
false,
29
3.29);
30
AddItem("Hotdog",
31
"A hot dog with saurkraut, relish, onions, topped with cheese",
32
false,
33
3.05);
34
AddItem("Steamed Veggies and Brown Rice",
35
"Steamed vegetables over brown rice",
36
true,
37
3.99);
38
AddItem("Pasta",
39
"Spaghetti with Marina Sauce and a slice of sourdough bread",
40
true,
41
3.89);
42
}
43
44
public void AddItem(string name, string description,
45
bool isVegetarian, double price)
46
{
47
MenuItem menuItem = new MenuItem(name,description,
48
isVegetarian,price);
49
if(numberOfItems >= MAX_ITEMS)
50
{
51
Console.WriteLine("Sorry, menu is full! Can't add item to menu");
52
}
53
else
54
{
55
menuItems[numberOfItems] = menuItem;
56
numberOfItems += 1;
57
}
58
}
59
60
public Iterator CreateIterator()
61
{
62
return new DinnerMenuIterator(menuItems);
63
}
64
}
65
}
66
DinnerMenuIterator:

Code
1
using System;
2
3
namespace HeadFirstDesignPatterns.Iterator.Menu
4

{
5
/**//// <summary>
6
/// Summary description for DinnerMenuIterator.
7
/// </summary>
8
public class DinnerMenuIterator :Iterator
9
{
10
MenuItem[] items;
11
int position = 0;
12
13
public DinnerMenuIterator(MenuItem[] items)
14
{
15
this.items = items;
16
}
17
18
Iterator Members#region Iterator Members
19
20
public bool HasNext()
21
{
22
if(position >= items.Length || items[position] == null)
23
{
24
return false;
25
}
26
else
27
{
28
return true;
29
}
30
}
31
32
public object Next()
33
{
34
MenuItem menuItem = items[position];
35
position += 1;
36
return menuItem;
37
}
38
39
#endregion
40
}
41
}
42
PancakeMenu和PancakeMenuIterator的代码和DinerMenu的类似。
总结起来,Iterator模式的本质就是将业务职责和遍历的职责分离,实现带一职责原则。同时也注意针对接口编程。
说了这么多的Iterator Pattern。现在我们再来看看在C#中是如何应用到这个模式的。
我们知道,在C#中,如果要遍历一个类的每个成员的话,这个类要实现IEnumerable接口,同时还要提供一个IEnumerator接口的实现。这个IEnumerable接口就相当于我们前面定义的Menu接口,只是告诉具体的实现类给我一个Iterator的实现,也就是C#中的IEnumerator的实现。所以说,IEnumerator就相当于Iterator接口。
比如说如果我们有一个object[] values = {"a", "b", "c", "d", "e"} IterationSample collection = new IterationSample(values, 3);
foreach(object x in collection)
{
Console.WriteLine(x);
}
执行这些代码将打印如下的信息:d e a b c
也就是说在遍历过程中根据指定的偏移量进行打印输出。按照C#中的方法,我们可以定义代码如下:
IterationSample:

Code
1
class IterationSample : IEnumerable
2
{
3
object[] values;
4
int startingPoint;
5
6
public IterationSample(object[] values, int startingPoint)
7
{
8
this.values = values;
9
this.startingPoint = startingPoint;
10
}
11
12
public IEnumerator GetEnumerator()
13
{
14
return new IterationSampleIterator(this);
15
}
16
}IterationSampleIterator:

Code
1
class IterationSampleIterator : IEnumerator
2
{
3
IterationSample parent;
4
int position;
5
6
internal IterationSampleIterator(IterationSample parent)
7
{
8
this.parent = parent;
9
position = -1;
10
}
11
12
public bool MoveNext()
13
{
14
if (position != parent.values.Length)
15
{
16
position++;
17
}
18
return position < parent.values.Length;
19
}
20
21
public object Current
22
{
23
get
24
{
25
if (position == -1 ||
26
position == parent.values.Length)
27
{
28
throw new InvalidOperationException();
29
}
30
int index = (position + parent.startingPoint);
31
index = index % parent.values.Length;
32
return parent.values[index];
33
}
34
}
35
36
public void Reset()
37
{
38
position = -1;
39
}
40
}这样我们就实现了整个Iterator Pattern。当然,这种实现只是针对C# 1。对于后面的版本提供了哪些简化和便利,且看下回。