zoukankan      html  css  js  c++  java
  • 设计模式的征途—21.迭代器(Iterator)模式

    我们都用过电视机遥控器,通过它我们可以进行开机、关机、换台、改变音量等操作。我们可以将电视机看做一个存储电视频道的集合对象,通过遥控器可以对电视机中的频道集合进行操作,例如返回上一个频道、跳转到下一个频道或者跳转到指定的频道等。遥控器的出现,使得用户不需要知道这些频道到底如何存储在电视机中。在软件开发中也存在类似于电视机一样的类,他们可以存储了多个成员对象(元素),这些类通常称为聚合类(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()方法。)

    参考资料

      DesignPattern

      (1)刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

      (2)圣杰,《C#设计模式之迭代器模式》

  • 相关阅读:
    Generate Parentheses
    Length of Last Word
    Maximum Subarray
    Count and Say
    二分搜索算法
    Search Insert Position
    Implement strStr()
    Remove Element
    Remove Duplicates from Sorted Array
    Remove Nth Node From End of List
  • 原文地址:https://www.cnblogs.com/edisonchou/p/7442138.html
Copyright © 2011-2022 走看看