我们都用过电视机遥控器,通过它我们可以进行开机、关机、换台、改变音量等操作。我们可以将电视机看做一个存储电视频道的集合对象,通过遥控器可以对电视机中的频道集合进行操作,例如返回上一个频道、跳转到下一个频道或者跳转到指定的频道等。遥控器的出现,使得用户不需要知道这些频道到底如何存储在电视机中。在软件开发中也存在类似于电视机一样的类,他们可以存储了多个成员对象(元素),这些类通常称为聚合类(Aggregate Class),对应的对象称为聚合对象。为了更加方便地操作这些聚合对象,同时可以很灵活地为聚合对象增加不同的遍历方法,也需要类似于电视机遥控器一样的角色,可以访问一个聚合对象中的元素担忧部需要暴露它的内部结构,这就是我们需要学习的迭代器模式。
迭代器模式(Iterator) | 学习难度:★★★☆☆ | 使用频率:★★★★★ |
一、销售管理系统中数据的遍历
Background : M公司为某商场开发了一套销售管理系统,在对该系统进行分析和设计时,M公司开发人员发现经常需要对系统中的商品数据、客户数据等进行遍历,为了复用这些遍历代码,M公司开发人员设计了一个抽象的数据聚合类AbstractObjectList,而将存储商品和客户登记的类作为其子类。AbstractObjectList类结构如下图所示。
在上图中,IList类型的对象objects用于存储数据,AbstractObjectList类的方法说明如下表所示:
AbstractObjectList类的子类ProductList和CustomerList分别用于存储商品数据和客户数据。
M公司开发人员通过对AbstractObjectList类结构进行分析,发现该设计方案存在以下问题:
(1)在该类中,AddObject()与RemoveObject()等方法用于管理数据,而GetNextItem()、GetPreviousItem()、IsFirst()等方法又用于遍历数据,导致了聚合类的职责过重,违反了单一职责原则。
(2)如果将抽象聚合类声明为一个接口,则在这个接口中充斥着大量方法,不利于子类实现,违反了接口隔离原则。
(3)如果将所有的遍历操作都交给子类来实现,将导致子类代码过于庞大,而且必须暴露AbstractObjectList类的内部存储细节,向子类公开自己的私有属性,否则子类无法实施对数据的遍历,将破坏AbstractObjectList类的封装性。
如何解决该问题?解决方案之一就是将聚合类中负责遍历数据的方法提取出来,封装到专门的类中,实现数据存储和数据遍历的分离,无须暴露聚合类的内部属性即可对其进行操作,这正是迭代器模式的意图所在。
二、迭代器模式概述
2.1 迭代器模式简介
在软件开发中,经常需要使用聚合对象来存储一系列数据。聚合对象拥有两个职责:一是存储数据,二是遍历数据。从依赖性来看,前者是聚合对象的基本职责,而后者既是可变化的又是可分离的。因此,可以将遍历数据的行为从聚合对象中分离出来,封装在一个被称为“迭代器”的对象中,由迭代器来提供遍历聚合对象内部数据的行为,这将简化聚合对象的设计,更加符合单一职责原则。
迭代器(Iterator)模式:提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。
2.2 迭代器模式结构
(1)Iterator(抽象迭代器):定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法。
(2)ConcreteIterator(具体迭代器):它实现了抽象迭代器接口,完成对聚合对象的遍历。
(3)Aggregate(抽象聚合类):用于存储和管理元素对象,声明一个CreateIterator()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
(4)ConcreteAggregate(具体聚合类):实现了在抽象聚合类中声明的CreateIterator()方法,返回一个对应的具体迭代器ConcreteIterator实例。
三、销售管理系统中数据的遍历实现
3.1 重构后的设计结构
其中,AbstractObjectList充当抽象聚合类,ProductList充当具体聚合类,AbstractIterator充当抽象迭代器,ProductIterator充当具体迭代器。
3.2 重构后的代码实现
(1)抽象聚合类:AbstractObjectList
/// <summary> /// 抽象聚合类:AbstractObjectList /// </summary> public abstract class AbstractObjectList { protected IList<object> objectList = new List<object>(); public AbstractObjectList (IList<object> objectList) { this.objectList = objectList; } public void AddObject(object obj) { this.objectList.Add(obj); } public void RemoveObject(object obj) { this.objectList.Remove(obj); } public IList<Object> GetObjectList() { return this.objectList; } // 声明创建迭代器对象的抽象工厂方法 public abstract AbstractIterator CreateIterator(); }
(2)具体聚合类 - ProductList 与 具体迭代器 - ProductIterator => 这里采用了内部类的方式
/// <summary> /// 具体聚合类:ProductList /// </summary> public class ProductList : AbstractObjectList { public ProductList(IList<object> objectList) : base(objectList) { } public override AbstractIterator CreateIterator() { return new ProductIterator(this); } /// <summary> /// 内部类=>具体迭代器:ProductIterator /// </summary> private class ProductIterator : AbstractIterator { private ProductList productList; private IList<object> products; private int cursor1; // 定义一个游标,用于记录正向遍历的位置 private int cursor2; // 定义一个游标,用于记录逆向遍历的位置 public ProductIterator(ProductList productList) { this.productList = productList; this.products = productList.GetObjectList(); // 获取集合对象 this.cursor1 = 0; // 设置正向遍历游标的初始值 this.cursor2 = this.products.Count - 1; // 设置逆向遍历游标的初始值 } public object GetNextItem() { return products[cursor1]; } public object GetPreviousItem() { return products[cursor2]; } public bool IsFirst() { return cursor2 == -1; } public bool IsLast() { return cursor1 == products.Count; } public void Next() { if (cursor1 < products.Count) { cursor1++; } } public void Previous() { if (cursor2 > -1) { cursor2--; } } } }
(3)抽象迭代器:AbstractIterator
/// <summary> /// 抽象迭代器:AbstractIterator /// </summary> public interface AbstractIterator { void Next(); // 移动至下一个元素 bool IsLast(); // 判断是否为最后一个元素 void Previous(); // 移动至上一个元素 bool IsFirst(); // 判断是否为第一个元素 object GetNextItem(); // 获取下一个元素 object GetPreviousItem(); // 获取上一个元素 }
(4)客户端测试
public class Program { public static void Main(string[] args) { IList<object> products = new List<object>(); products.Add("倚天剑"); products.Add("屠龙刀"); products.Add("断肠草"); products.Add("葵花宝典"); products.Add("四十二章经"); AbstractObjectList objectList = new ProductList(products); // 创建聚合对象 AbstractIterator iterator = objectList.CreateIterator(); // 创建迭代器对象 Console.WriteLine("正向遍历"); while (!iterator.IsLast()) { Console.Write(iterator.GetNextItem() + ","); iterator.Next(); } Console.WriteLine(); Console.WriteLine("-------------------------------------------------------"); Console.WriteLine("逆向遍历"); while (!iterator.IsFirst()) { Console.Write(iterator.GetPreviousItem() + ","); iterator.Previous(); } Console.ReadKey(); } }
F5编译运行后的结果如下图所示:
四、迭代器模式小结
4.1 主要优点
(1)支持以不同方式遍历一个聚合对象,在同一个聚合对象上可以定义多种便利方式。
(2)增加新的聚合类和迭代器类都很方便 => 无须修改原有代码,符合开闭原则。
4.2 主要缺点
增加新的聚合类需要对应增加新的迭代器类 => 类的个数会成对增加!
4.3 应用场景
(1)访问一个聚合对象的内容而无须暴露它的内部表示。
(2)需要为一个聚合对象提供多种遍历方式。
(3)重点 => 该模式在.Net中,可以通过实现IEnumberable接口即可,不再需要单独实现! (在.NET下,迭代器模式中的聚集接口和迭代器接口都已经存在了,其中IEnumerator接口扮演的就是迭代器角色,IEnumberable接口则扮演的就是抽象聚集的角色,其中定义了GetEnumerator()方法。)
参考资料
(1)刘伟,《设计模式的艺术—软件开发人员内功修炼之道》
(2)圣杰,《C#设计模式之迭代器模式》