迭代器模式

[TOC]

示例

合并菜单(C#实现)

需求说明

某连锁餐厅有一家早餐店和一家晚餐店,现需要将早餐店和晚餐店合并,由于早餐和晚餐其数据结构不同,现在需要一个统一的菜单,即菜单项结构相同

数据结构说明

合并后的菜单单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MenuItem
{
public MenuItem(string name, string description, bool vegetarin, double price)
{
Name = name;
Description = description;
IsVegetarian = vegetarin;
Price = price;
}

/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 价格
/// </summary>
public double Price { get; set; }
/// <summary>
/// 是否素食
/// </summary>
public bool IsVegetarian { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; }
}

早餐类,结构是ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BreakfastMenu
{
private readonly ArrayList _menuItems;

public BreakfastMenu()
{
_menuItems = new ArrayList();
AddItem("牛奶", "牛奶description", false, 3.0);
AddItem("油条", "油条description", false, 1.0);
AddItem("馒头", "馒头description", true, 1.0);
AddItem("豆浆", "豆浆description", true, 1.5);
}

private void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);
_menuItems.Add(menuItem);
}

public ArrayList GetMenuItems()
{
return _menuItems;
}
}

晚餐类,结构是数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class DinnerMenu 
{
private const int MaxItems = 6;
int _numberOfItems = 0;
private readonly MenuItem[] _menuItems;

public DinerMenu()
{
menuItems = new MenuItem[Max_ITEMS];
AddItem("香菇豆腐饭", "香菇豆腐", false, 10.5);
AddItem("蛋炒饭", "哈哈", false, 8.5);
AddItem("鱼香肉丝", "你猜", true, 15.5);
}

public void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);

if (_numberOfItems > MaxItems)
{
Console.WriteLine("菜单已满");
}
else
{
_menuItems[_numberOfItems++] = menuItem;
}
}

public MenuItem[] GetMenuItems()
{
return menuItems;
}
}

原有的客户端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//不使用迭代器
var breakfastMenu = new BreakfastMenu();
ArrayList breakfastItems = breakfastMenu.GetMenuItems();

var dinnerMenu = new DinnerMenu();
MenuItem[] dinnerItems = dinnerMenu.GetMenuItems();

foreach (var breakfastItem in breakfastItems)
{
if (breakfastItem is MenuItem menuItem)
{
Console.WriteLine($"{menuItem.Name} {menuItem.Price} {menuItem.Description}");
}
}

foreach (var dinnerItem in dinnerItems)
{
if (dinnerItem != null)
{
Console.WriteLine($"{dinnerItem.Name} {dinnerItem.Price} {dinnerItem.Description}");
}
}

上面的遍历的算法是一样的,因为早餐和晚餐的数据结构的不同导致了代码不能复用

如果以后还要将一个午餐厅餐单合并到菜单中(数据结构和早餐晚餐都不同),又要修改代码,可以使用泛型解决该问题。但是这里使用的是迭代器设计模式

使用迭代器模式实现

定义一个迭代器接口

1
2
3
4
5
6
7
8
9
10
11
12
13
interface ITerator 
{
/// <summary>
/// 用来判断下一个元素是否为空
/// </summary>
/// <returns></returns>
bool HasNext();
/// <summary>
/// 用来获取当前元素
/// </summary>
/// <returns></returns>
object Next();
}

我们希望的是能通过迭代器实现下面的操作

1
2
3
4
5
while (iterator.HasNext())
{
var menuItem = (MenuItem)iterator.Next();
Console.WriteLine($"{menuItem.Name} {menuItem.Price} {menuItem.Description}");
}

创建早晚餐菜单的迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BreakfastIterator : ITerator 
{
private readonly ArrayList _items;
private int _position;

public BreakfastIterator(ArrayList arrayList)
{
_items = arrayList;
}

public bool HasNext()
{
return _position < _items.Count && _items[_position] != null;
}

public object Next()
{
return _items[_position++] as MenuItem;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DinnerIterator : ITerator 
{
private readonly MenuItem[] _items;
private int _position = 0;

public DinnerIterator(MenuItem[] items)
{
_items = items;
}

public bool HasNext()
{
return _position < _items.Length && _items[_position] != null;
}

public object Next()
{
return _items[_position++];
}
}

定义一个菜单接口,来创建迭代器,返回迭代器接口

1
2
3
4
interface IMenu   
{
ITerator CreateIterator();
}

在早餐和晚餐的菜单中实现这个菜单接口

1
2
3
4
5
6
7
8
9
10
public class BreakfastMenu : IMenu
{
//其余部分省略
//...

public ITerator CreateIterator()
{
return new BreakfastIterator(menuItems);
}
}
1
2
3
4
5
6
7
8
9
10
public class DinnerMenu : IMenu
{
//其余部分省略
//...

public ITerator CreateIterator()
{
return new DinnerIterator(menuItems);
}
}

注意在 BreakfastMenuDinnerMenu 类中去掉了 GetMenuItems() 方法.因为在改造前的实现中我们需要GetMenuItems()提供聚集数据 ,但是正是因为两个类的GetMenuItems() 返回的数据结构不同,造成了代码不能复用

改造后BreakfastMenuDinnerMenu 类只是单纯的存放聚集数据,我们用更抽象的ITerator的来返回单个数据,避免 数据的不一致,ITerator 类的Next()方法 接管了原来GetMenuItems()方法的 功能


客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void Main(string[] args) 
{
//使用迭代器
var breakfastMenu = new BreakfastMenu();
var dinnerMenu = new DinnerMenu();

var dinnerIterator = dinnerMenu.CreateIterator();
var breakfastIterator = breakfastMenu.CreateIterator();


Console.WriteLine("早餐:");
Print(breakfastIterator);
Console.WriteLine();
Console.WriteLine("晚餐:");
Print(dinnerIterator);

Console.ReadKey();
}

static void Print(ITerator iterator)
{
while (iterator.HasNext())
{
var menuItem = (MenuItem)iterator.Next();
Console.WriteLine($"{menuItem.Name} {menuItem.Price} {menuItem.Description}");
}
}

迭代器模式主要是聚合对象创建迭代器,借助单一职责的原则,从而实现客户端可以对聚合的各种对象实现相同的操作,达到代码复用的效果。

类图对比

上面代码的类图

迭代器模式的类图

.NET中的迭代器模式

.NET1.1

在上面的例子中,我们没有使用任何.NET 的特性

实际要在.NET内部已经定义了实现Iterator模式 需要的聚集接口和迭代器接口,其中IEnumerator 扮演的就是迭代器接口的角色,也就是上文中的ITerator

1
2
3
4
5
6
7
8
9
10
11
12
//System.Collections.IEnumerator
public interface IEnumerator
{
object Current
{
get;
}

bool MoveNext();

void Reset();
}

属性Current 返回当前集合中的元素,Reset() 方法恢复初始化指向的位置,MoveNext() 方法返回值true 表示迭代器成功前进到集合中的下一个元素,返回值false 表示已经位于集合的末尾

IEnumerable 则扮演的就是抽象聚集接口的角色,相当于上文中的IMenu,只有一个GetEnumerator() 方法,如果集合对象需要具备跌代遍历的功能,就必须实现该接口。

1
2
3
4
public interface IEnumerable
{
IEumerator GetEnumerator();
}

我们试着使用.NET1.1 的方式来改写合并菜单的例子,因为有了系统接口,我们可以少写两个接口,使用系统接口

迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class BreakfastIterator : IEnumerator 
{
private readonly ArrayList _items;
private int _position = -1;

public BreakfastIterator(ArrayList arrayList)
{
_items = arrayList;
}

public bool MoveNext()
{
_position++;
return _position < _items.Count;
}

public object Current => _items[_position];

public void Reset()
{
_position = -1;
}
}

public class DinnerIterator : IEnumerator
{
private MenuItem[] items;
private int _position = -1;

public DinnerIterator(MenuItem[] items)
{
this.items = items;
}

public bool MoveNext()
{
_position++;
return _position < items.Length && items[_position] != null;
}

public object Current => items[_position];

public void Reset()
{
_position = -1;
}
}

这里为了方便迭代,_position 的初始值设置成了-1,因为MoveNext 函数同时完成了移动指针和判断是否能继续移动的功能

聚集数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class BreakfastMenu : IEnumerable 
{
private readonly ArrayList _menuItems;

public BreakfastMenu()
{
_menuItems = new ArrayList();
AddItem("牛奶", "牛奶description", false, 3.0);
AddItem("油条", "油条description", false, 1.0);
AddItem("馒头", "馒头description", true, 1.0);
AddItem("豆浆", "豆浆description", true, 1.5);
}

public void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);
_menuItems.Add(menuItem);
}

public IEnumerator GetEnumerator()
{
return new BreakfastIterator(menuItems);
}
}

public class DinnerMenu : IEnumerable
{
private const int MaxItems = 6;
private int _numberOfItems = 0;
private readonly MenuItem[] _menuItems;

public DinnerMenu()
{
_menuItems = new MenuItem[MaxItems];
AddItem("香菇豆腐饭", "香菇豆腐", false, 10.5);
AddItem("蛋炒饭", "哈哈", false, 8.5);
AddItem("鱼香肉丝", "你猜", true, 15.5);
}

public void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);

if (_numberOfItems > MaxItems)
{
Console.WriteLine("菜单已满");
}
else
{
_menuItems[_numberOfItems++] = menuItem;
}
}

public IEnumerator GetEnumerator()
{
return new DinnerIterator(menuItems);
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Program 
{
static void Main(string[] args)
{
//使用.NET自带接口
var breakfastMenu = new BreakfastMenu();
var dinnerMenu = new DinnerMenu();

Console.WriteLine("早餐:");
foreach (MenuItem menuItem in breakfastMenu)
{
Print(menuItem);
}

Console.WriteLine("\n晚餐:");
foreach (MenuItem menuItem in dinnerMenu)
{
Print(menuItem);
}

Console.ReadKey();
}

static void Print(MenuItem menuItem)
{
Console.WriteLine($"{menuItem.Name} {menuItem.Price} {menuItem.Description}");
}
}

可以看出上面使用迭代器模式实现时使用的迭代器类没有了,while (iterator.HasNext()) {...} 也没有了,foreach 究竟使用了什么魔法呢?

看一下这段对应的IL代码

IL代码 比较难看懂,重点关注红线的部分,所以大概等价于这么回事(只分析breakfastMenu 部分)

1
2
3
4
5
6
7
BreakfastMenu breakfastMenu = new BreakfastMenu ();
IEnumerator e = breakfastMenu.GetEnumerator ();

while (e.MoveNext ())
{
Print ((MenuItem) e.Current);
}

看出来了么?.NET 帮我们在底层实现了调用接口方法并迭代,foreach只是语法糖

不过,尽管我们少写了很多代码,这里的实现和不用特性的代码相比,在代码结构上仍然高度相似,并且我们把大部分的精力都花在了实现迭代器和可迭代的类上面.到了NET2.0 ,由于有了yield return 关键字,实现起来将更加的简单优雅.

.NET2.0

有了yield return可以这么实现聚集数据:

注意GetEnumerator方法中使用了yield return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class BreakfastMenu : IEnumerable 
{
ArrayList _menuItems;
public BreakfastMenu()
{
_menuItems = new ArrayList();
AddItem("牛奶", "牛奶description", false, 3.0);
AddItem("油条", "油条description", false, 1.0);
AddItem("馒头", "馒头description", true, 1.0);
AddItem("豆浆", "豆浆description", true, 1.5);
}

private void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);
_menuItems.Add(menuItem);
}

public IEnumerator GetEnumerator()
{
for (int i = 0; i < _menuItems.Length; i++)
{
yield return _menuItems[i];
}
}
}

public class DinnerMenu : IEnumerable
{
static readonly int MaxItems = 6;
private int _numberOfItems = 0;
private readonly MenuItem[] _menuItems;

public DinnerMenu()
{
_menuItems = new MenuItem[MaxItems];
AddItem("香菇豆腐饭", "香菇豆腐", false, 10.5);
AddItem("蛋炒饭", "哈哈", false, 8.5);
AddItem("鱼香肉丝", "你猜", true, 15.5);
}

private void AddItem(string name, string description, bool vegetarian, double price)
{
var menuItem = new MenuItem(name, description, vegetarian, price);
if (_numberOfItems > MaxItems)
{
Console.WriteLine("菜单已满");
}
else
{
_menuItems[_numberOfItems++] = menuItem;
}
}

public IEnumerator GetEnumerator()
{
for (int i = 0; i < _menuItems.Length; i++)
{
yield return _menuItems[i];
}
}
}

客户端调用不变,注意因为是固定数组所以要判一下空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Program 
{
static void Main(string[] args)
{
//使用yield return
var breakfastMenu = new BreakfastMenu();
var dinnerMenu = new DinnerMenu();

Console.WriteLine("早餐:");
foreach (MenuItem menuItem in breakfastMenu)
{
Print(menuItem);
}

Console.WriteLine("\n晚餐:");
foreach (MenuItem menuItem in dinnerMenu)
{
if (menuItem == null)
{
continue;
}
Print(menuItem);
}

Console.ReadKey();
}

static void Print(MenuItem menuItem)
{
Console.WriteLine($"{menuItem.Name} {menuItem.Price} {menuItem.Description}");
}
}

这次我们连迭代器接口都省略了!

yield又是个啥?

只看这一段

1
2
3
4
5
6
7
public IEnumerator GetEnumerator() 
{
for (int i = 0; i < _menuItems.Length; i++)
{
yield return _menuItems[i];
}
}

对应的IL代码

有点看不懂了,不过还是发现了一点蛛丝马迹,红线的部分,状态机!

是的编译器在幕后构建了一个状态机,隐藏了复杂性.原理就是记录下程序状态,下次迭代从状态处开始,这样来实现MoveNext

作为理解原理,到这里已经足够了.如果要知道yield 的具体实现,必须要去研究CLI 的内容

详细看一下这一段状态机是怎么实现的

  • this.<>1__state 代表内部的状态,下面记做state
  • this.<>4__this 代表当前类的实例
  • this.<i>E5__1 代表当前迭代的下标,下面记做index
  • this.<>2__current 代表当前的迭代结果item

state的初始状态为0,state为0时给index初始值0

index>=menuItems.Count时返回false

其余情况给state设为1,返回true

state的初始状态为1时++index

还是很好理解的

可见 C#一直在努力改进语法糖,让我们能更方便的写代码.但是其实,内部实现了迭代器模式

实现自己的迭代器(js和ruby实现)

需求:判断两个已经排序的数组里的元素是否完全相同

1.实现一个each 函数

each函数接受两个参数,第一个为被循环的数组,第二个为循环中每一步后将触发的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
var each = function(arr, callback) {

for (var i = 0; i < arr.length; i++) {
//把下标和元素当作参数传给callback函数
callback.call(arr[i], i, arr[i]);
}
};

//调用
each([1, 2, 3], function(i, n) {
console.log([i, n]);
});

2.内部迭代器和外部迭代器

内部迭代器

上面的each函数属于内部迭代器,外界不需关心迭代器内部实现

然而,我们需要稍微改动each函数才能实现我们的需求

1
2
3
4
5
6
7
8
var each = function(arr, callback) {

for (var i = 0; i < arr.length; i++) {
if (callback(arr[i], i) === false) {
return false;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var isSameArray = function(arr1, arr2) {

if (arr1.length !== arr2.length) {
return false;
}

var isSame = each(arr1, function(item, index) {

if (item !== arr2[index]) {
return false;
}
});

if (isSame === false) {
return false;
}

return true;
};

//调用
console.log(isSameArray([1,2,3], [1,2,3]));//true
console.log(isSameArray([1,2,3], [1,2,4]));//false

上面的isSameArray函数实现的相当难看,能够实现功能得益于在js 中可以把函数当作参数传递的特性,但是在其他语言中未必可行

在没有闭包的语言中,内部迭代器本身的实现相当复杂,比如C语言实现内部迭代器就就需要用到函数指针,循环处理所需要的数据都要以参数的形式明确地从外面传递进去

外部迭代器

外部迭代器必须显式地请求迭代下一个元素。
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序

下面这个外部迭代器的实现来自《松本行弘的程序世界》第4 章,原例用Ruby 写成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ArrayIterator
def initialize(array)
@array = array
@current = 0
end

def first()
@current = 0
end

def next()
@current += 1
end

def is_done()
return @current >= @array.size()
end

def current_item
return @array.get(@current)
end
end

改成js 并且实现的上面的需求

ES5版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var Iterator = function(arr) {
var index = 0;

var next = function() {
index++;
};

var hasNext = function() {
return index < arr.length;
};

var getCurrentItem = function() {
return arr[index];
};

return {
next: next,
hasNext: hasNext,
getCurrentItem: getCurrentItem
};
};

var isSameArray = function(iterator1, iterator2) {

while (iterator1.hasNext() && iterator2.hasNext()) {

if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
return false;
}

iterator1.next();
iterator2.next();
}

return true;
};

//调用
var iterator1 = Iterator([1, 2, 3]);
var iterator2 = Iterator([1, 2, 3]);
var iterator3 = Iterator([1, 4, 3]);
var iterator4 = Iterator([1, 5, 3]);

console.log(isSameArray(iterator1, iterator2));//true
console.log(isSameArray(iterator3, iterator4));//false

ES6版本,其实这里Iterator最好用class实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const Iterator = arr => {
let index = 0;

const next = () => {
index++;
};

const hasNext = () => index < arr.length;

const getCurrentItem = () => arr[index];

return {
next,
hasNext,
getCurrentItem
};
};

const isSameArray = (iterator1, iterator2) => {

while (iterator1.hasNext() && iterator2.hasNext()) {

if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
return false;
}

iterator1.next();
iterator2.next();
}

return true;
};

//调用
const iterator1 = Iterator([1, 2, 3]);
const iterator2 = Iterator([1, 2, 3]);
const iterator3 = Iterator([1, 4, 3]);
const iterator4 = Iterator([1, 5, 3]);

console.log(isSameArray(iterator1, iterator2));//true
console.log(isSameArray(iterator3, iterator4));//false

浏览器上传组件(js实现)

重构某个项目中文件上传模块的代码时,发现了下面这段代码,它的目的是根据不同的浏览器获取相应的上传组件对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
var getUploadObj = function () {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
} catch (e) {
if (supportFlash()) { // supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($('body'));
} else {
var str = '<input name="file" type="file"/>'; // 表单上传
return $(str).appendTo($('body'));
}
}
}

为了得到一个upload 对象,这个getUploadObj 函数里面充斥了try,catch以及if 条件分支。缺点是显而易见的:

  • 第一是很难阅读
  • 第二是严重违反开闭原则

后来我们还增加了一些另外的上传方式,比如HTML5 上传,此时唯一的办法是继续往getUploadObj 函数里增加条件分支。

现在来梳理一下问题,目前一共有3 种可能的上传方式,我们不知道目前正在使用的浏览器支持哪几种。于是从第一种方式开始,迭代所有方式进行尝试,直到找到了正确的方式为止。做法如下

  • 把每种获取upload 对象 的方法都封装在各自的函数里
  • 使用一个迭代器,迭代获取这些upload 对象 ,直到获取到一个可用的为止:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var getActiveUploadObj = function () {
    try {
    return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
    } catch (e) {
    return false;
    }
    };

    var getFlashUploadObj = function () {
    if (supportFlash()) { // supportFlash 函数未提供
    var str = '<object type="application/x-shockwave-flash"></object>';
    return $(str).appendTo($('body'));
    }
    return false;
    };

    var getFormUpladObj = function () {
    var str = '<input name="file" type="file" class="ui-file"/>'; // 表单上传
    return $(str).appendTo($('body'));
    };

getActiveUploadObjgetFlashUploadObjgetFormUpladObj 这3 个函数中都有同一个约定:
如果该函数里面的upload 对象是可用的,则让函数返回该对象,反之返回false,提示迭代器继续往后面进行迭代。

所以我们的迭代器只需进行下面这几步工作

  1. 提供一个可以被迭代的方法,使得getActiveUploadObjgetFlashUploadObj 以及getFlashUploadObj
    依照优先级被循环迭代

  2. 如果正在被迭代的函数返回一个对象,表示找到了正确的upload 对象,反之如果该函数返回false,则让迭代器继续工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var iteratorUploadObj = function () {

    for (var i = 0, fn; fn = arguments[i++];) {
    var uploadObj = fn();

    if (uploadObj !== false) {
    return uploadObj;
    }
    }
    };

    var uploadObj = iteratorUpload(getActiveUploadObj, getFlashUploadObj, getFormUpladObj);

    重构代码之后,获取不同上传对象的方法被隔离在各自的函数里互不干扰,try、catch 和if 分支不再纠缠在一起。

    并且我们可以很方便地的维护和扩展代码。比如,后来我们又给上传项目增加了Webkit 控件上传和HTML5 上传,我们要做的仅仅是下面一些工作。

    • 增加分别获取Webkit 控件上传对象和HTML5 上传对象的函数:

      1
      2
      3
      4
      5
      6
      var getWebkitUploadObj = function(){
      // 具体代码略
      };
      var getHtml5UploadObj = function(){
      // 具体代码略
      };
    • 依照优先级把它们添加进迭代器:

      1
      2
      3
      4
      5
      6

      var uploadObj = iteratorUploadObj( getActiveUploadObj,
      getWebkitUploadObj,
      getFlashUploadObj,
      getHtml5UploadObj,
      getFormUpladObj );

计数器(python实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/"""

"""Implementation of the iterator pattern with a generator"""


def count_to(count):
"""Counts by word numbers, up to a maximum of five"""
numbers = ["one", "two", "three", "four", "five"]
# enumerate() returns a tuple containing a count (from start which
# defaults to 0) and the values obtained from iterating over sequence
for pos, number in zip(range(count), numbers):
yield number

# Test the generator
count_to_two = lambda: count_to(2)
count_to_five = lambda: count_to(5)

print()
print('Counting to two...')
for number in count_to_two():
print(number, end=' ')

print()
print()

print('Counting to five...')
for number in count_to_five():
print(number, end=' ')

print()