迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
- 一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。
- 你需要对聚集有多种方式遍历时,可以考虑用迭代器模式。为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
- 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
既然,迭代器模式承担了遍历集合对象的职责,则该模式自然存在2个类,一个是聚合类,一个是迭代器类。在面向对象涉及原则中还有一条是针对接口编程,所 以,在迭代器模式中,抽象了2个接口,一个是聚合接口,另一个是迭代器接口,这样迭代器模式中就四个角色了,具体的类图如下所示:
从上图可以看出,迭代器模式由以下角色组成:
- 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口
- 具体迭代器角色(Concrete Iteraror):具体迭代器角色实现了迭代器接口,并需要记录遍历中的当前位置。
- 聚合角色(Aggregate):聚合角色负责定义获得迭代器角色的接口
- 具体聚合角色(Concrete Aggregate):具体聚合角色实现聚合角色接口。
在下面的情况下可以考虑使用迭代器模式:
- 系统需要访问一个聚合对象的内容而无需暴露它的内部表示。
- 系统需要支持对聚合对象的多种遍历。
- 系统需要为不同的聚合结构提供一个统一的接口。
C#迭代器模式:
namespace 迭代器模式 { class Program { static void Main(string[] args) { ConcreteAggregate a = new ConcreteAggregate(); a[0] = "大鸟"; a[1] = "小菜"; a[2] = "行李"; a[3] = "老外"; a[4] = "公交内部员工"; a[5] = "小偷"; Iterator i = new ConcreteIterator(a); //Iterator i = new ConcreteIteratorDesc(a); object item = i.First(); while (!i.IsDone()) { Console.WriteLine("{0} 请买车票!", i.CurrentItem()); i.Next(); } Console.Read(); } } abstract class Aggregate { public abstract Iterator CreateIterator(); } class ConcreteAggregate : Aggregate { private IList<object> items = new List<object>(); public override Iterator CreateIterator() { return new ConcreteIterator(this); } public int Count { get { return items.Count; } } public object this[int index] { get { return items[index]; } set { items.Insert(index, value); } } } abstract class Iterator { public abstract object First(); public abstract object Next(); public abstract bool IsDone(); public abstract object CurrentItem(); } class ConcreteIterator : Iterator { private ConcreteAggregate aggregate; private int current = 0; public ConcreteIterator(ConcreteAggregate aggregate) { this.aggregate = aggregate; } public override object First() { return aggregate[0]; } public override object Next() { object ret = null; current++; if (current < aggregate.Count) { ret = aggregate[current]; } return ret; } public override object CurrentItem() { return aggregate[current]; } public override bool IsDone() { return current >= aggregate.Count ? true : false; } } class ConcreteIteratorDesc : Iterator { private ConcreteAggregate aggregate; private int current = 0; public ConcreteIteratorDesc(ConcreteAggregate aggregate) { this.aggregate = aggregate; current = aggregate.Count - 1; } public override object First() { return aggregate[aggregate.Count - 1]; } public override object Next() { object ret = null; current--; if (current >= 0) { ret = aggregate[current]; } return ret; } public override object CurrentItem() { return aggregate[current]; } public override bool IsDone() { return current < 0 ? true : false; } } }
C#.NET的迭代器实现:
namespace NET的迭代器实现 { class Program { static void Main(string[] args) { IList<string> a = new List<string>(); a.Add("大鸟"); a.Add("小菜"); a.Add("行李"); a.Add("老外"); a.Add("公交内部员工"); a.Add("小偷"); foreach (string item in a) { Console.WriteLine("{0} 请买车票!", item); } IEnumerator<string> e = a.GetEnumerator(); while (e.MoveNext()) { Console.WriteLine("{0} 请买车票!", e.Current); } Console.Read(); } } }
js内部迭代器:
现在我们来实现一个each函数,each函数接受2个参数,第一个为被循环的数组,第二个为循环中的每一步后将被触发的回调函数:
var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ callback.call(ary[i],i,ary[i]); //把下标和元素当作参数传递给callback函数 } }; each([1,2,3],function(i,n){ alert([i,n]); });
js外部迭代器:
比如现在有个需求,要判断2个数组里的元素的值是否完全相等,先看一下不使用外部迭代器的写法:
var compare = function(ary1,ary2){ if(ary1.length !== ary2.length){ throw new Error('ary1和ary2不相等'); } each(ary1,function(i,n){ if(n!==ary2[i]){ throw new Error('ary1和ary2不相等'); } }); alert('ary1和ary2相等'); }; compare([1,2,3],[1,2,4]); //throw new Error('ary1和ary2不相等');
说实话,这个compare函数一点都算不上好看,我们目前能够顺利的完成需求,还要感谢在javascript里可以把函数当作参数传递的特性,但在其他的语言中未必就能如此幸运。
在一些没有闭包的语言中,内部迭代器本身的实现也相当复杂,比如C语言中的内部迭代器是用函数指针来实现的,循环处理需要的数据都要以参数的形式明确地从外面传递进去。
外部迭代器必须显式地请求迭代下一个元素。
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
下面这个外部迭代器的实现来自《松本行弘的程序世界》第4章,原例用Ruby写成,这里我们翻译成javascript:
var Iterator = function(obj){ var current = 0; var next = function(){ current += 1; }; var isDone = function(){ return current >= obj.length; }; var getCurrItem = function(){ return obj[current]; }; return{ next:next, isDone:isDone, getCurrItem:getCurrItem } }; 再看看如何改写compare函数: var compare = function(iterator1,iterator2){ while(!iterator1.isDone() && !iterator2.isDone()){ if(iterator1.getCurrItem() !== iterator2.getCurrItem()){ throw new Error('iterator1和iterator2不相等'); } iterator1.next(); iterator2.next(); } alert('iterator1和iterator2相等'); }; var iterator1 = Iterator([1,2,3]); var iterator2 = Iterator([1,2,3]); compare(iterator1,iterator2); //输出:iterator1和iterator2相等
外部迭代器虽然调用方式相对复杂,但它的适用面更广,也能满足更多变的需求。内部迭代器和外部迭代器在实际生产中没有优劣之分,究竟使用哪个要根据需求场景而定。
迭代类数组对象和字面量对象:
var isArraylike = function(obj){ return Object.prototype.toString.call(obj) === '[object Array]'; }; var each = function(obj,callback){ var value, i = 0, length = obj.length, isArray = isArraylike(obj); if(isArray){ //迭代类数组 for(;i<length;i++){ value = callback.call(obj[i],i,obj[i]); if(value === false){ break; } } }else{ for(i in obj){ value = callback.call(obj[i],i,obj[i]); if(value === false){ break; } } } }; each([1,2,3,4,5],function(i,n){ if(n>3){ return false; } alert(n); //分别输出:1,2,3 }); each({a:1,b:2,c:3,d:4,e:5},function(i,n){ if(n>3){ return false; } alert(n); //分别输出:1,2,3 });
js倒序迭代器:
var reverseEach = function(ary,callback){ for(var l=ary.length-1;l>=0;l--){ callback.call(ary[l],l,ary[l]); } }; reverseEach([0,1,2],function(i,n){ alert(n); //分别输出:2,1,0 });
js中止迭代器:
var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ if(callback.call(ary[i],i,ary[i]) === false){ //callback的执行结果返回false,提前终止迭代 break; } } }; each([1,2,3,4,5],function(i,n){ if(n>3){ return false; } alert(n); //分别输出:1,2,3 });