前言:这两天学习 Iterator 模式,不知道是吕震宇老师没写这一篇,还是我没找到,就想到了把自己的学习体会拿出来与大家分享,我也是初学者,希望大家能提出意见。另外转载请注明作者和出处,毕竟花了快一周的时间才完成。
一、两个概念
1、聚集:所谓聚集就是一组数据集或者对象集,它可以通过循环来访问 。
2、枚举器: 专门用来访问聚集的类,他包装了一定的方法,可以依次把聚集中的数据按照一定的顺序读出来。
二、枚举器模式中出现的接口和类
1、Aggregate 接口:抽象的聚集,通常只留有一个方法让子类去实现,这个方法的作用是获得一个枚举器对象,通常可以起名字为 GetIterator( ),CreateIterator( ) 等等,或者干脆叫做 Iterator( ) 也行。在 C# 中,这个方法的名字叫做 GetEnumerator( ) ,这个我们到后面再讲。当然,在获得枚举器的同时,也要把自己 this(一组数据集)当作参数传给枚举器,想想也是,如果没有数据集,枚举器去枚举什么呢?
2、ConcreteAggregate 类:具体的聚集,它的实例保存了一组数据集,同时它实现了 Aggregate 接口中唯一的那个方法,通常情况下他也会扩展出一些其他方法便于访问聚集中的数据,常见的有:访问某个位置数据的方法,可以叫做 GetElement(int index) 或者 GetItem(int index) 等等;获得聚集大小的方法,可以起名字为 GetLength( ) 或者 GetSize( ) 等等,全看自己喜好。
3、Iterator 接口:抽象的枚举器,通常情况下会有三个方法留给子类去实现,他们分别是:Next( ) ,用来把指针移动到聚集中的下一个数据;HasNext( ) ,用来判断是否还有下一个数据;CurrentItem( ),返回当前指针所指位置的数据。也可以把 Next( ) 和 CurrentItem( ) 组成一个方法,在移动指针的同时返回一个数据。也可以有其他的实现方式,或者简单,或者复杂,也是全看个人需求。
4、ConcreteIterator 类:具体的枚举器,它实现了上述的三个方法,通过不同的实现方式,我们可以获得不同的枚举方式,如顺序枚举、倒序枚举等等。当然,这个类的构造方法中会接受一个具体聚集参数,想想也是,如果没有这个数据集,他去枚举什么呢?这个参数就是他要去枚举的对象。
5、被聚集的类,这个可以是任何类,它的许多个对象被存到聚集对象中才能形成一个真正的聚集,在我的例子中用的类叫 Book 类,它的四个实例即四本书形成一个聚集。下面看具体的代码吧。
三、常规枚举器模式的代码实现
这里用到“常规”这个词是指,这里的代码可以当作一个模版来用,不管你用的是 C# 还是 Java 或者是 Delphi ,均可以按照这个模版来实现枚举器模式。当然在 C# 和 Java 中,因为它们本身就支持枚举器模式,实现起来更方便一些,C# 的实现方法会在后面给出。
我把客户端代码和模式类代码分别放在两个文件中。
下面是模式类代码 Iterator.cs
namespace Iterator
{
/// <summary>
/// 一个普通的书籍类,它的许多实例可以形成一个聚集
/// </summary>
public class Book
{
string name;
public Book(string name)
{
this.name=name;
}
public string GetName()
{
return name;
}
}
/// <summary>
/// 抽象的聚集接口
/// </summary>
public interface IAggregate
{
IIterator GetIterator();
}
/// <summary>
/// 书架,它是一个具体的聚集类,在这里是书的聚集
/// 所有聚集的共同点是,它会创建一个枚举器
/// 同时把自己当作参数传给枚举器,好让这个枚举器可以枚举自己
/// </summary>
public class BookShelf:IAggregate
{
private Book[] books;
public BookShelf()
{
books=new Book[4];
books[0]=new Book("Around the World in 80 Days");
books[1]=new Book("Bible");
books[2]=new Book("Cinderella");
books[3]=new Book("Dady-long-legs");
}
public Book GetElement(int index)
{
return books[index];
}
public int GetLength()
{
return books.Length ;
}
public IIterator GetIterator()
{
// 把自己 一个书架实例 传到一个具体的枚举器中,好让枚举器工作
return new BookShelfIterator(this);
}
}
/// <summary>
/// 枚举器接口,抽象的,由它去枚举聚集
/// </summary>
public interface IIterator
{
bool HasNext();
Object CurrentItem();
void Next();
}
/// <summary>
/// 具体的枚举器,在这里是一个书架
/// 所有枚举器的特点是,他会在构造方法中得到一个具体的聚集实例,并用一定的方法去枚举他
/// </summary>
public class BookShelfIterator:IIterator
{
private BookShelf bookShelf;
private int index;
private int size;
public BookShelfIterator(BookShelf bookShelf)
{
//获得一个具体聚集,去枚举他
this.bookShelf=bookShelf;
index=0;
size=bookShelf.GetLength();
}
public bool HasNext()
{
if(index<size) return true;
else return false;
}
public Object CurrentItem()
{
return bookShelf.GetElement(index);
}
public void Next()
{
if(index<size) index++;
}
}
}
下面是客户端代码:Client.cs
namespace Iterator
{
class MainClass
{
[STAThread]
static void Main(string[] args)
{
IIterator iterator;
IAggregate agg=new BookShelf();//创建一个书架,书架上已经存了四本书
iterator=agg.GetIterator();//创建一个可以枚举这个书架的枚举器
while(iterator.HasNext())
{
Book book=(Book)iterator.CurrentItem();
Console.WriteLine(book.GetName());
iterator.Next();
}
Console.Read();
}
}
}
四、在 C#中枚举器模式的实现——使用 foreach 语句
在 C# 中,因为 Aggregate 接口和 Iterator 接口,已经存在了,我们直接去实现他就可以了。 C# 中的Aggregate 接口叫做 IEnumerable,翻译过来是可以被枚举的,什么可以被枚举?当然就是聚集了。Iterator 接口叫做 IEnumerator ,是不是换汤不换药?如果上面的代码您已经看懂了,下面的代码应该也难不住你,我就不多说了,有注释。
我把客户端代码和模式类代码分别放在两个文件中。
下面是模式类代码 Iterator.cs
using System.Collections ;
namespace Iterator
{
public class Book
public class BookShelf:IEnumerable
{
private Book[] books;
public BookShelf()
{
books=new Book[4];
books[0]=new Book("Around the World in 80 Days");
books[1]=new Book("Bible");
books[2]=new Book("Cinderella");
books[3]=new Book("Dady-long-legs");
}
public Book GetElement(int index)
{
return books[index];
}
public int Length
{
get
{
return books.Length ;
}
}
//实现 IEnumerable 接口的方法,得到一个枚举器,并把自己作为参数传给枚举器
public IEnumerator GetEnumerator()
{
Console.WriteLine("得到枚举器!");
return new BookShelfIterator(this);;
}
}
public class BookShelfIterator:IEnumerator
{
private BookShelf bookShelf;
private int index;
//得到一个聚集对象,可以去枚举他
public BookShelfIterator(BookShelf bookShelf)
{
this.bookShelf=bookShelf;
index=-1;
}
//实现 IEnumerator 的方法,将指针移动到下一个
public bool MoveNext()
{
if(index<bookShelf.Length -1)
{
Console.Write("MoveNext->");
index++;
return true;
}
else return false;
}
//实现 IEnumerator 的方法,将指针重置到起始位置
public void Reset()
{
index=-1;
Console.WriteLine("Reset!");
}
//实现 IEnumerator 的属性,返回当前指针所指的数据
public object Current
{
get
{
Console.Write("GetCurrent->");
return bookShelf.GetElement(index);
}
}
}
}
下面是客户端代码:Client.cs
namespace Iterator
{
class MainClass
{
[STAThread]
static void Main(string[] args)
{
BookShelf agg=new BookShelf();//创建一个书架,书架上已经存了四本书
foreach(Book book in agg) Console.WriteLine(book.Name);
Console.Read();
}
}
}
关于这两段代码,用分步运行就可以搞清楚 foreach 是怎么运作的了。但我还是要解释一下。
1、首先 in 后面的变量一定得是个聚集,即必须返回一个 IEnumerable 类型的对象。在这里我们直接给它了,就是 agg 。但当你想实现多种枚举方式的时候,就必须知道这一点。所以再重复一遍,in 后面的变量必须是一个 IEnumerable 类型的对象。
2、当循环开始启动时, 首先运行的是 in 后面那个聚集对象的 GetEnumerator( ) 方法,它返回一个枚举器,并把聚集自身当作参数传给枚举器。这个枚举器到底返回给谁了?我不知道,但是有一点我很清楚,这个方法 return 的是哪个枚举器的实例, foreach 就会用哪个枚举器的方式去枚举数据。当你想实现多种枚举方式的时候,你必须知道这一点。这里我们只用到了一个枚举器,后面我们会举一个两个枚举器的例子。
3、当确定使用哪个枚举器以后, foreach 接着会调用这个枚举器的 MoveNext( ) 方法,什么都没干先移动一下?很奇怪吧?就是这样!所以注意 index 要从 -1 开始。
4、每 MoveNext( ) 一下,接着就调用 Current 属性,返回当前指针指向的数据。
5、当 MoveNext( ) 返回 false 时,一个 foreach 过程就结束了。
运行效果如下图:
五、一个聚集实现多个枚举器——使用 foreach 语句
这一部分我不作讲解了,除了上面的功能外,多实现的一个倒序的枚举器,如果你能看懂上一部分的5点,这里的代码就很容易理解了。在C# 2.0中还有更好地实现方式,但是不适合用于讲解模式,有兴趣的朋友可以自己去查一些资料。另外总觉得这段代码应该不是很漂亮,有修改的余地,请高手赐教。
下面是模式类代码 Iterator.cs
using System.Collections ;
namespace Iterator
{
/// <summary>
///
/// </summary>
public class Book
{
string name;
public Book(string name)
{
this.name=name;
}
public string Name
{
get
{
return name;
}
}
}
/// <summary>
///
/// </summary>
public class BookShelf:IEnumerable
{
private Book[] books;
private IEnumerator iterator;
public BookShelf()
{
books=new Book[4];
books[0]=new Book("Around the World in 80 Days");
books[1]=new Book("Bible");
books[2]=new Book("Cinderella");
books[3]=new Book("Dady-long-legs");
iterator=new DefaulIterator(this);
}
public Book GetElement(int index)
{
return books[index];
}
public int Length
{
get
{
return books.Length ;
}
}
public IEnumerator GetEnumerator()
{
Console.WriteLine("得到枚举器!");
return iterator;
}
public IEnumerable GetOrderBooks()
{
iterator=new DefaulIterator(this);
return this;
}
public IEnumerable GetReverseBooks()
{
iterator=new OtherIterator(this);
return this;
}
}
/// <summary>
///
/// </summary>
public class DefaulIterator:IEnumerator
{
private BookShelf bookShelf;
private int index;
public DefaulIterator(BookShelf bookShelf)
{
//获得一个具体聚集,去枚举他
this.bookShelf=bookShelf;
index=-1;
}
public bool MoveNext()
{
if(index<bookShelf.Length -1)
{
Console.Write("MoveNext->");
index++;
return true;
}
else return false;
}
public void Reset()
{
index=-1;
Console.WriteLine("Reset!");
}
public object Current
{
get
{
Console.Write("GetCurrent->");
return bookShelf.GetElement(index);
}
}
}
/// <summary>
///
/// </summary>
public class OtherIterator:IEnumerator
{
private BookShelf bookShelf;
private int index;
public OtherIterator(BookShelf bookShelf)
{
//获得一个具体聚集,去枚举他
this.bookShelf=bookShelf;
index=4;
}
public bool MoveNext()
{
if(index>0)
{
Console.Write("MoveNext->");
index--;
return true;
}
else return false;
}
public void Reset()
{
index=4;
Console.WriteLine("Reset!");
}
public object Current
{
get
{
Console.Write("GetCurrent->");
return bookShelf.GetElement(index);
}
}
}
}
下面是客户端代码:Client.cs
namespace Iterator
{
class MainClass
{
[STAThread]
static void Main(string[] args)
{
BookShelf agg=new BookShelf();//创建一个书架,书架上已经存了四本书
foreach(Book book in agg.GetOrderBooks()) Console.WriteLine(book.Name);
foreach(Book book in agg.GetReverseBooks()) Console.WriteLine(book.Name);
Console.Read();
}
}
}
运行效果如下图: