迭代器模式(Iterator)和组合模式(Composite)

在面向对象设计中,总是会要使用集合对象的,这些集合对象的内部结构可能是非常复杂的,

但是作为客户端使用的话,我们只需要关心集合内部的数据和遍历集合内部的数据就可以了,

如果以最简单的设计来实现的话,大可以定义一个类,类中定义一个诸如数组,栈,队列之类的对象,

然后将数据的存储以及数据的遍历全部交给这一个类来完成,但是在面向对象中还有这样一个设计原则,

也就是单一责任原则(对于一个类来说,应该仅有一个能够引起这个类变化的原因)

简单的说,一个类只负责一块责任或者说是功能,

当一个类负责超过一个责任时,意味着这个类将会有多个可能改变这个类的潜在区域。

所以,为了在面向对象设计中更合理更好的使用集合对象,并且解决一个集合对象承担太多责任这个问题,

从而就有了迭代器模式。迭代器从一定层次上讲也就是一个遍历操作。

迭代器模式呢,就是通过将一个集合对象的内部结构以及遍历这个集合对象这两个责任进行分离。

          

            

迭代器模式的定义

迭代器模式(Iterator)提供一种方法顺序访问一个聚合对象中的各个元素,

并且不暴露该对象的内部表示。

可以通过迭代器模式来访问一个集合对象,并且您不需要知道这些对象是什么。

同时,迭代器模式把在遍历集合元素的责任交给了迭代器而非聚合对象,

这不仅让集合的接口变得更加简单,同时也可以使得集合类符合单一责任原则,

因为它不再需要去理会遍历集合的这些操作了。

        

             

迭代器模式的结构类图

image

从上面的类图中可以看出,我们定义了两个抽象类,一个是聚集(也就是集合)类 AbstractAggregate ,

还有一个就是迭代器类 AbstractIterator 类,

对于聚集类来说,它是用来存放数据的,也就是说是一个简单的只用了存放数据的地方,

而至于访问这个集合中的数据的话,就是通过迭代器 AbstractIterator 来完成了,

换句话说就是分工明确,这样上面的两个类也就都符合单一责任原则了。

在 AbstractAggregate 中定义了一个用了创建迭代器的方法 CreateIterator ,

在 AbstractIterator 中定义了得到第一个元素,下一个元素,是否结束,当前的元素等等方法。

        

         

下面就来看看迭代器模式具体的实现

首先要看的是抽象集合类和具体集合类,也就是 AbstractAggregate 和 ConcreteAggregate 两个类,

namespace Iterator
{
    public abstract class AbstractAggregate
    {
       
/// <summary>
        /// 用来创建一个迭代器
        /// </summary>
        /// <returns></returns>

        public abstract AbstractIterator CreateIterator();
    }
}

           

using System.Collections.Generic;

namespace Iterator
{
    public class
ConcreteAggregate:AbstractAggregate
    {
        /// <summary>
        /// 定义一个泛型集合来充当容器
        /// </summary>
      

        private IList list = new List();

        /// <summary>
        /// 用来在当前这个集合上创建一个迭代器
        /// </summary>
        /// <returns></returns>

        public override
AbstractIterator CreateIterator()
        {
            return new ConcreteIterator(this);
        }

          

        /// <summary>
        /// 用来获取整个聚集的大小
        /// </summary>

        public int Count
        {
            get
            {
                return list.Count;
            }
        }

          

        /// <summary>
        /// 在这里定义了一个索引器
        /// 可以用来实现添加一个值到指定索引处
        /// 同时也可以获取指定索引处的值
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public object this[int index]
        {
            get
            {
                return list[index];
            }
            set
            {
                list.Insert(index, value);
            }
        }

    }
}

下面就要贴出抽象迭代器和具体迭代器的代码了

namespace Iterator
{
    public abstract class AbstractIterator
    {
        /// <summary>
        /// 得到聚集对象中的第一个元素
        /// </summary>
        /// <returns></returns>

        public
abstract object First();
        /// <summary>
        /// 得到聚集对象中当前元素的下一个元素
        /// </summary>
        /// <returns></returns>

        public
abstract object Next();
        /// <summary>
        /// 判断聚集对象是否遍历到了最后一个元素
        /// </summary>
        /// <returns></returns>

        public
abstract bool IsOver();
 
      /// <summary>
        /// 获取聚集对象中当前遍历到的元素
        /// </summary>
        /// <returns></returns>

        public abstract object CurrentItem();
    }
}

            

namespace Iterator
{
    public class
ConcreteIterator : AbstractIterator
    {
        /// <summary>
        /// 需要在迭代器类中维护一个具体的集合类
        /// 这样才能让迭代器知道需要进行迭代的对象
        /// </summary>

        private ConcreteAggregate aggregate;
        //定义初始指针
        private int currentIndex = 0;

        public ConcreteIterator(ConcreteAggregate aggregate)
        {
            this.aggregate = aggregate;
        }

        public override object First()
        {
            return this.aggregate[0];
        }

        public override object Next()
        {
            //需要将指针移动到下一个元素上
            currentIndex++;
            if (currentIndex < this.aggregate.Count)
            {
                return this.aggregate[this.currentIndex];
            }
            return null;
        }

        public override bool IsOver()
        {
            if (this.currentIndex < this.aggregate.Count)
            {
                return false;
            }
            return true;
        }

        public override object CurrentItem()
        {
            return this.aggregate[this.currentIndex];
        }
    }
}

最后就是来看一下客户端代码和效果演示了

using System;

namespace IteratorTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //首先要定义一个具体的集合对象
            Iterator.ConcreteAggregate aggregate =
                new Iterator.ConcreteAggregate();

            //下面就是往这个集合对象中插入值
            aggregate[0] = "星期一";
            aggregate[1] = "星期二";
            aggregate[2] = "星期三";
            aggregate[3] = "星期四";
            aggregate[4] = "星期五";
            aggregate[5] = "星期六";
            aggregate[6] = "星期天";

            //通过这个集合对象中的 CreateIterator
            //来创建一个集合对象自身的迭代器

            Iterator.AbstractIterator iterator =
                aggregate.CreateIterator();

            //下面就是一个迭代的过程了
            object item = iterator.First();
            Console.WriteLine(iterator.CurrentItem());
            while (!iterator.IsOver())
            {
                Console.WriteLine(iterator.Next());
            }

            Console.Read();
        }
    }
}

image

上面就是迭代器模式的具体实现的全部代码了,不过要注意的是,

现在迭代器模式被觉得越来越不重要了,这是因为由于迭代器的经常使用,所以在 Java 和 C# 这些高级语言中,

便把迭代器写进了语法中了,比如我们经常使用的 foreach…..in 就是一个典型的使用迭代器模式。

所以现在,几乎很少再单独的写一个迭代器模式来实现遍历了,而都是使用高级语言提供的用来遍历集合对象的语法。

一般都是如下使用了

using System;
using System.Collections.Generic;

namespace IteratorTest
{
    class Program
    {
        static void Main(string[] args)
        {

            IList list = new List();
            list.Add("星期一");
            list.Add("星期二");
            list.Add("星期三");
            list.Add("星期四");
            list.Add("星期五");
            list.Add("星期六");
            list.Add("星期天");

      

            foreach (object o in list)
            {
                Console.WriteLine(o);
            }
            Console.Read();
        }
    }
}

这里呢就是直接使用了 foreach 来实现的迭代,整个程序的效果和前面的 Demo 的效果是一样的。

还有要提一下的是,在 . Net 中,Iterator也就是迭代器模式是如何实现的?

在 . Net 下实现迭代器模式呢,其实和上面的 Demo 也差不多,也需要有聚集接口和迭代器接口,

只不过,. Net 已经提供了这些接口而不再需要自己编写而已了,

在 . Net 的迭代器模式中充当抽象聚集的接口是 IEnumerable 接口,

而充当抽象迭代器接口的是 IEnumerator,

先来看 IEnumerable 和 IEnumerator 这两个接口在 . Net 中的定义

image

image

从上面的两幅截图中可以看出,对于 IEnumerable 接口,其只定义了一个 GetEnumerator 的方法,

而对于 IEnumerator 接口的话,其内定义了多个方法和属性。

在这里就不针对这两个方法写类了,不过下面给出一个结构类图,

大家可以通过这个结构类图来实现一个 Demo 从而实现使用 . Net 下提供的迭代器模式。

image

           

               

下面再来总结一下迭代器模式

首先呢,迭代器模式是用来实现顺序访问一个集合对象中的元素的,并且它不需要知道这些元素在集合内部是如何表示的,

同时,通过使用迭代器模式可以将集合类中的管理元素和遍历访问元素两个职责进行分离,从而使得集合类可以满足单一责任原则,

并且在不暴露集合内部结构的同时实现遍历集合。

最后就是迭代器模式已经在 C# 语言中有了实现,抽象集合接口和抽象迭代器接口都有现成的提供。

              

              

               

下面要介绍的是一个新的模式了,组合模式

在很多时候,都会遇到整体和部分需要被一致对待的情况。

比如,一个火锅店,其拥有总公司和多个分公司,那么这个总公司就可以看作是一个整体,

而这些分公司就可以看作是一个部分了,但是,总公司和分公司的人事管理,财务管理等等很多都是相同的,

所以,在这里便出现了整体和部分需要被一致对待的情况

(不然要针对总公司和分公司开发不同的管理系统,这样太麻烦了吧)。

由此便可以引出组合模式(Composite)。

        

       

还是先来看结构类图吧,这样比较清晰

image

通过组合模式,可以使用树形方式来创建对象的结构,树里面包含了组合(有枝节点)以及个别的对象(叶子节点),

叶子节点是不会再有子节点的,而组合对象也就是 Composite 的话,它定义了有枝节点,并且用来存储子节点,

这些子节点可以是叶子节点,也可以是 Composite 组合对象。

通过组合对象,我们便可以忽略掉叶子节点和有枝节节点之间的差别,而进行一致性的使用,

也就是,对 Leaf 和对 Composite 在客户端的使用是没有区别的,是一致的,因为它们都继承子 Component 基类。

        

            

下面先来看组合模式的定义吧

组合模式(Composite)也叫做合成模式或者是整体-部分模式(Part-Whole),

它将对象组合成树形结构并且使用这个树形结构来表示“整体-部分”的层次结构,

组合模式使得用户对单个对象和组合对象的使用具有一致性。

           

             

下面就来看组合模式的具体实现了

首先来看的是 Component 类

namespace Composite
{
    public abstract class Component
    {
        protected string componentName;

        public Component(string name)
        {
            this.componentName = name;
        }

       /// <summary>
        /// 添加一个组合对象
        /// </summary>
        /// <param name="component"></param>

        public abstract void Add(Component component);
  
     /// <summary>
        /// 移除一个组合对象
        /// </summary>
        /// <param name="component"></param>

        public abstract void Remove(Component component);
        /// <summary>
        /// 获得所有的孩子节点
        /// </summary>

        public abstract void GetAllChild();
    }
}

再来看 Leaf 和 Composite 类

using System;

namespace Composite
{
    public class
Leaf : Component
    {
        public Leaf(string name)
            : base(name)
        {

        }

        /// <summary>
        /// 叶子节点没有孩子
        /// 所以无法添加和移除孩子
        /// </summary>
        /// <param name="component"></param>

        public override void Add(Component component)
        {

        }

        public override void Remove(Component component)
        {

        }

        /// <summary>
        /// 同时叶子节点没有孩子节点
        /// </summary>

        public override void GetAllChild()
        {
            Console.WriteLine("{0} 是叶子节点,没有孩子", this.componentName);
        }
    }
}

          

using System;
using System.Collections.Generic;

namespace Composite
{
    public class Composite:Component
    {
 
       /// <summary>
        /// 使用一个 List 泛型数组来实现存储所有的孩子
        /// </summary>

        private List<Component> list = new List<Component>();

        public Composite(string name)
            : base(name)
        {
        }

        /// <summary>
        /// 添加一个孩子
        /// </summary>
        /// <param name="component"></param>

        public override void Add(Component component)
        {
            list.Add(component);
        }

        /// <summary>
        /// 移除一个孩子
        /// </summary>
        /// <param name="component"></param>

        public override void Remove(Component component)
        {
            list.Remove(component);
        }

        /// <summary>
        /// 获取所有的孩子
        /// </summary>

        public override void GetAllChild()
        {
            Console.WriteLine("{0} 是组合对象,它拥有的孩子如下:", this.componentName);
            foreach (Component component in list)
            {
                component.GetAllChild();
            }

        }
    }
}

客户端代码和效果演示

using System;

namespace CompositeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Composite.Composite root = new Composite.Composite("根节点");
            //定义一个组合孩子 A ,且其拥有两个叶子节点孩子
            Composite.Composite compositeA = new Composite.Composite("组合孩子A");
            compositeA.Add(new Composite.Leaf("组合孩子A的叶子节点A"));
            compositeA.Add(new Composite.Leaf("组合孩子A的叶子节点B"));

           //将组合孩子 A 定义为根节点的孩子
            root.Add(compositeA);

            root.Add(new Composite.Leaf("叶子节点 C"));
            root.Add(new Composite.Leaf("叶子节点 D"));

            root.GetAllChild();

           
            Console.WriteLine();

            

            compositeA.GetAllChild();
            Console.ReadKey();
        }
    }
}

image

从上面在客户端的代码和具体的效果演示可以看出,(请结合代码和效果图仔细观摩)

在客户端并不会区分 Leaf 和 Composite ,如果把 Leaf 看作简单元素而把 Composite 看作复杂元素的话,

那么,反映的就是在客户端并不会区分简单元素还是负责元素,而是将他们一致对待,

或者说是以处理简单元素的方式来处理复杂元素,而这正是使用组合模式的好处所在。

其实呢,从上面的代码中可以看出,在获取整个孩子节点时,也就是在 GetAllChild 方法中是使用了 foreach 的,

而这正是一种迭代,之所以会把迭代模式和组合模式放到一篇博文中来做介绍,

也是因为这一点,不过,由于在高级语言中已经很好的实现了对集合对象的迭代遍历,

所以不再需要单独的使用迭代器模式和组合模式进行组合完成迭代组合的功能了,

因为您只需要简单的 foreach 就可以完成的功能,又何必再来写一大堆代码完成呢?

还有要点一下的是,组合模式其实还有一个透明方式和安全方式之分的,

何为透明方式呢?

透明方式就是在 Component 类中声明所有用来管理子对象的方法,就比如上面的 Demo 就是使用的透明方式,

因为我在 Component 类中声明了 Add 啊,Remove 啊等等方法,这样做的好处是,

对于叶子节点和组合对象节点来说,它们对于客户端是没有区别的,因为他们实现了完全一致的对外接口

(也就是 Add ,Remove 这些接口),只不过,因为叶子节点没有孩子,

所以 Add ,Remove 这些对于叶子节点也就失去了存在的意义(所以在我的 Leaf 类中,Add ,Remove 的方法体都是空的)

而后就是安全方式了,

安全方式也就是不再在 Component 中定义诸如 Add ,Remove 这些方法了,而是在 Composite 中直接定义,

这样的话,便不需要再在 Leaf 中重写这些对于 Leaf 没有作用的方法了,

但是这样也会带来问题的,那就是,”它不够透明“,在客户端看来,Leaf 和 Composite 就不再是一致的了。

客户端在调用时,可能需要添加更多的 if else 来判断到底是 Leaf 还是 Composite 。

哎呀,很晚了啊,明天还有的忙,所以就不写 Demo 了,

不过,为了完整,还是给出一个链接,链接呢,是链接到李会军大哥介绍组合模式的博客上,

有兴趣的可以观摩观摩~~~~~~

http://www.cnblogs.com/Terrylee/archive/2006/03/11/347919.html

 

posted @ 2010-05-14 02:49  小宝马的爸爸  阅读(2249)  评论(1编辑  收藏