本文摘取自TerryLee(李会军)老师的设计模式系列文章,版权归TerryLee,仅供个人学习参考。转载请标明原作者TerryLee。部分示例代码来自DoFactory。
概述
在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据。面向对象设计原则中有一条是类的单一职责原则,所以我们要尽可能的去分解这些职责,用不同的类去承担不同的职责。Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。
意图
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
UML
Iterator模式UML图如下:
图1 Iterator模式UML图
参与者
这个模式涉及的类或对象:
-
Iterator
-
定义一个遍历元素的接口。
-
ConcreteIterator
-
实现Iterator接口。
-
在遍历集合过程中记录当前位置。
-
Aggregate
-
定义创建Iterator对象的接口
-
ConcreteAggregate
-
实现创建Iterator的接口并返回恰当的ConcreteIterator的实例。
适用性
遍历一个对象集合并操作其中的对象是一个常见的编程任务。这些集合可能以数组、列表或一些更复杂的,如树或图这样的结构来存储。此外,你可能需要以特定顺序访问集合中的项,如,从前向后,从后向前,深度优先(在树查找中),跳过偶数位置的对象等。迭代器模式通过实现一个专用的迭代器类从而将对象集合与遍历这些对象相分离来解决上述问题。
不仅是你已经发现迭代器模式已经深深的结合在.NET类库中,这是仅有的两个作为C#和VB.NET语言特性本身被支持的模式之一(另一个是观察者模式)。两种语言都有一个内置的构造简化迭代结合的过程,分别是C#种的foreach和VB.NET中的For Each。如这段简单的示例代码:
string[] votes = new string[] { "Agree", "Disagree", "Don’t Know" }; foreach (string vote in votes) { Console.WriteLine(vote); }
注意,in表达式后面的集合对象必须实现IEnumerable接口,从而使对象集合可以被遍历。
下面列出了一些具体需要迭代器模式的场景。
-
访问一个聚合对象的内容而无需暴露它的内部表示。
-
支持对聚合对象的多种遍历。
-
为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
DoFactory GoF代码
这段示例代码展示了迭代器提供的遍历一个对象的集合而无需关注集合的底层结构的方法。
// Iterator pattern // Structural example using System; using System.Collections; namespace DoFactory.GangOfFour.Iterator.Structural { // MainApp test application class MainApp { static void Main() { ConcreteAggregate a = new ConcreteAggregate(); a[0] = "Item A"; a[1] = "Item B"; a[2] = "Item C"; a[3] = "Item D"; // Create Iterator and provide aggregate ConcreteIterator i = new ConcreteIterator(a); Console.WriteLine("Iterating over collection:"); object item = i.First(); while (item != null) { Console.WriteLine(item); item = i.Next(); } // Wait for user Console.ReadKey(); } } // "Aggregate" abstract class Aggregate { public abstract Iterator CreateIterator(); } // "ConcreteAggregate" class ConcreteAggregate : Aggregate { private ArrayList _items = new ArrayList(); public override Iterator CreateIterator() { return new ConcreteIterator(this); } // Gets item count public int Count { get { return _items.Count; } } // Indexer public object this[int index] { get { return _items[index]; } set { _items.Insert(index, value); } } } // "Iterator" abstract class Iterator { public abstract object First(); public abstract object Next(); public abstract bool IsDone(); public abstract object CurrentItem(); } // "ConcreteIterator" class ConcreteIterator : Iterator { private ConcreteAggregate _aggregate; private int _current = 0; // Constructor public ConcreteIterator(ConcreteAggregate aggregate) { this._aggregate = aggregate; } // Gets first iteration item public override object First() { return _aggregate[0]; } // Gets next iteration item public override object Next() { object ret = null; if (_current < _aggregate.Count - 1) { ret = _aggregate[++_current]; } return ret; } // Gets current iteration item public override object CurrentItem() { return _aggregate[_current]; } // Gets whether iterations are complete public override bool IsDone() { return _current >= _aggregate.Count; } } }
这段代码展示了使用迭代器模式遍历一个对象集合并可以给遍历指定一个"步长"(每次遍历跳过指定数量的元素)。
例子中涉及到的类与迭代器模式中标准的类对应关系如下:
-
Iterator - AbstractIterator
-
ConcreteIterator - Iterator
-
Aggregate - AbstractCollection
-
ConcreteAggregate - Collection
// Iterator pattern // Real World example using System; using System.Collections; namespace DoFactory.GangOfFour.Iterator.RealWorld { // MainApp test application class MainApp { static void Main() { // Build a collection Collection collection = new Collection(); collection[0] = new Item("Item 0"); collection[1] = new Item("Item 1"); collection[2] = new Item("Item 2"); collection[3] = new Item("Item 3"); collection[4] = new Item("Item 4"); collection[5] = new Item("Item 5"); collection[6] = new Item("Item 6"); collection[7] = new Item("Item 7"); collection[8] = new Item("Item 8"); // Create iterator Iterator iterator = new Iterator(collection); // Skip every other item iterator.Step = 2; Console.WriteLine("Iterating over collection:"); for (Item item = iterator.First(); !iterator.IsDone; item = iterator.Next()) { Console.WriteLine(item.Name); } // Wait for user Console.ReadKey(); } } // A collection item class Item { private string _name; // Constructor public Item(string name) { this._name = name; } // Gets name public string Name { get { return _name; } } } // 'Aggregate' interface interface IAbstractCollection { Iterator CreateIterator(); } // "ConcreteAggregate" class Collection : IAbstractCollection { private ArrayList _items = new ArrayList(); public Iterator CreateIterator() { return new Iterator(this); } // Gets item count public int Count { get { return _items.Count; } } // Indexer public object this[int index] { get { return _items[index]; } set { _items.Add(value); } } } // 'Iterator' interface interface IAbstractIterator { Item First(); Item Next(); bool IsDone { get; } Item CurrentItem { get; } } // "ConcreteIterator" class Iterator : IAbstractIterator { private Collection _collection; private int _current = 0; private int _step = 1; // Constructor public Iterator(Collection collection) { this._collection = collection; } // Gets first item public Item First() { _current = 0; return _collection[_current] as Item; } // Gets next item public Item Next() { _current += _step; if (!IsDone) return _collection[_current] as Item; else return null; } // Gets or sets stepsize public int Step { get { return _step; } set { _step = value; } } // Gets current iterator item public Item CurrentItem { get { return _collection[_current] as Item; } } // Gets whether iteration is complete public bool IsDone { get { return _current >= _collection.Count; } } } }
这个.NET优化的例子实现了与上面示例相同的功能但使用了更现代的.NET内置的特性。对于这个例子,IEnumerable和IEnumerator接口使用.NET内置的泛型迭代器模式来实现。
C#通过内置的yield return关键字支持迭代器,使用这个关键字可以让实现IEnumerable和IEnumerator接口更简单(甚至对于遍历树及其它复杂的数据结构也同样有效)。代码中三个返回IEnumerable<T>的泛型方法展示了实现正向遍历,反向遍历,或按步长遍历的优雅方式。
// Iterator pattern // .NET Optimized example using System; using System.Collections; using System.Collections.Generic; namespace DoFactory.GangOfFour.Iterator.NETOptimized { class MainApp { static void Main() { // Create and item collection var collection = new ItemCollection<Item> { new Item{ Name = "Item 0"}, new Item{ Name = "Item 1"}, new Item{ Name = "Item 2"}, new Item{ Name = "Item 3"}, new Item{ Name = "Item 4"}, new Item{ Name = "Item 5"}, new Item{ Name = "Item 6"}, new Item{ Name = "Item 7"}, new Item{ Name = "Item 8"} }; Console.WriteLine("Iterate front to back"); foreach (var item in collection) { Console.WriteLine(item.Name); } Console.WriteLine(" Iterate back to front"); foreach (var item in collection.BackToFront) { Console.WriteLine(item.Name); } Console.WriteLine(); // Iterate given range and step over even ones Console.WriteLine(" Iterate range (1-7) in steps of 2"); foreach (var item in collection.FromToStep(1, 7, 2)) { Console.WriteLine(item.Name); } Console.WriteLine(); // Wait for user Console.ReadKey(); } } /// <summary> /// The 'ConcreteAggregate' class /// </summary> /// <typeparam name="T">Collection item type</typeparam> class ItemCollection<T> : IEnumerable<T> { private List<T> _items = new List<T>(); public void Add(T t) { _items.Add(t); } // The 'ConcreteIterator' public IEnumerator<T> GetEnumerator() { for (int i = 0; i < Count; i++) { yield return _items[i]; } } public IEnumerable<T> FrontToBack { get { return this; } } public IEnumerable<T> BackToFront { get { for (int i = Count - 1; i >= 0; i--) { yield return _items[i]; } } } public IEnumerable<T> FromToStep(int from, int to, int step) { for (int i = from; i <= to; i = i + step) { yield return _items[i]; } } // Gets number of items public int Count { get { return _items.Count; } } // System.Collections.IEnumerable member implementation IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } // The collection item class Item { // Gets or sets item name public string Name { get; set; } } }
Iterator模式解说
在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据。面向对象设计原则中有一条是类的单一职责原则,所以我们要尽可能的去分解这些职责,用不同的类去承担不同的职责。Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。下面看一个简单的示意性例子,类结构图如下:
图2.迭代器模式示例UML图
首先有一个抽象的聚集,所谓的聚集就是就是数据的集合,可以循环去访问它。它只有一个方法GetIterator()让子类去实现,用来获得一个迭代器对象。
// 抽象聚集 public interface IList { IIterator GetIterator(); }
抽象的迭代器,它是用来访问聚集的类,封装了一些方法,用来把聚集中的数据按顺序读取出来。通常会有MoveNext()、CurrentItem()、Fisrt()、Next()等几个方法让子类去实现。
// 抽象迭代器 public interface IIterator { bool MoveNext(); Object CurrentItem(); void First(); void Next(); }
具体的聚集,它实现了抽象聚集中的唯一的方法,同时在里面保存了一组数据,这里我们加上Length属性和GetElement()方法是为了便于访问聚集中的数据。
// 具体聚集 public class ConcreteList : IList { int[] list; public ConcreteList() { list = new int[] { 1, 2, 3, 4, 5 }; } public IIterator GetIterator() { return new ConcreteIterator(this); } public int Length { get { return list.Length; } } public int GetElement(int index) { return list[index]; } }
具体迭代器,实现了抽象迭代器中的四个方法,在它的构造函数中需要接受一个具体聚集类型的参数,在这里面我们可以根据实际的情况去编写不同的迭代方式。
// 具体迭代器 public class ConcreteIterator : IIterator { private ConcreteList list; private int index; public ConcreteIterator(ConcreteList list) { this.list = list; index = 0; } public bool MoveNext() { if (index < list.Length) return true; else return false; } public Object CurrentItem() { return list.GetElement(index); } public void First() { index = 0; } public void Next() { if (index < list.Length) { index++; } } }
简单的客户端程序调用:
// 客户端程序 class Program { static void Main(string[] args) { IIterator iterator; IList list = new ConcreteList(); iterator = list.GetIterator(); while (iterator.MoveNext()) { int i = (int)iterator.CurrentItem(); Console.WriteLine(i.ToString()); iterator.Next(); } Console.Read(); } }
一个简单的迭代器示例就结束了,这里我们并没有利用任何的.NET特性,在C#中,实现Iterator模式已经不需要这么麻烦了,已经C#语言本身就有一些特定的实现,下面会说到。
.NET中的Iterator模式
正如上文提到的,迭代器模式不仅是.NET Framework类库的一部分,它已深入到语言本身。.NET类库中包含很多实现了IEnumerable和IEnumerator接口的类型,如,Array、ArrayList、AttributesColleciton、BaseChannelObjectWithProperties,BaseCollection、BindingsContext以及这些类的泛型版本。.NET 3.0引入的构建于泛型迭代器类型IEnumerable<T>和IEnumerator<T>上的查询语言LINQ使迭代更有趣更强大。
在.NET下实现Iterator模式,对于聚集接口和迭代器接口已经存在了,其中IEnumerator扮演的就是迭代器的角色,它的实现如下:
public interface IEumerator { object Current { get; } bool MoveNext(); void Reset(); }
属性Current返回当前集合中的元素,Reset()方法恢复初始化指向的位置,MoveNext()方法返回值true表示迭代器成功前进到集合中的下一个元素,返回值false表示已经位于集合的末尾。能够提供元素遍历的集合对象,在.Net中都实现了IEnumerator接口。
IEnumerable则扮演的就是抽象聚集的角色,只有一个GetEnumerator()方法,如果集合对象需要具备跌代遍历的功能,就必须实现该接口。
public interface IEnumerable { IEumerator GetEnumerator(); }
下面看一个迭代器例子,Person类是一个可枚举的类。PersonsEnumerator类是一个枚举器类。在.NET 2.0下面,由于有了yield return关键字,实现起来将更加的简单优雅。
public class Persons : IEnumerable { string[] m_Names; public Persons(params string[] Names) { m_Names = new string[Names.Length]; Names.CopyTo(m_Names, 0); } public IEnumerator GetEnumerator() { foreach (string s in m_Names) { yield return s; } } } class Program { static void Main(string[] args) { Persons arrPersons = new Persons("Michel", "Christine", "Mathieu", "Julien"); foreach (string s in arrPersons) { Console.WriteLine(s); } Console.ReadLine(); } }
程序将输出:
Michel
Christine
Mathieu
Julien
效果及实现要点
-
迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
-
迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
-
迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。
总结
Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。