想走?可以!先买票——迭代器模式

想走?可以!先买票——迭代器模式

继上一篇“分公司=一部门——组合模式”后,本文继续讲解《大话设计模式》第20章“想走?可以!先买票——迭代器模式”。喜欢本书请到各大商城购买原书,支持正版。

大话设计模式前期读书系列:

本文正式开始


1 乘车买票,不管你是谁!

时间:5月26日10点 地点:一部公交车上 人物:小菜、大鸟、售票员、公交乘客、小偷

这天是周末,小菜和大鸟一早出门游玩,上了公交车,车内很拥挤。

“上车的乘客请买票。”售票员一边在人缝中穿插着,一边说道。

………

“先生,您这包行李太大了,需要补票的。”售票员对一位拿着大包行李的乘客说道。

“哦,这也需要买票呀,它又不是人。”带大包行李的乘客说。

“您可以看看规定,当您携带的行李占据了一个客用面积时,请再购买同程车票一张,谢谢合作。”售票员指了指车上的一个纸牌子。

这位乘客很不情愿地再买了一张票。

“还有三位乘客没有买票,请买票!”

……

“这售票员够厉害,记得这么清楚,上来几个人都记得。”小菜感叹道。

“这也是业务能力的体现呀。”大鸟解释说。

……

“先生请买票!”售票员对着一位老外说道。

“Sorry,What do you say?”老外看来不会中文。

“请买车票怎么说?”售票员低声的自言自语道,“Please buy……票怎么说……”

“ticket.”小菜手掌放嘴边,小声地提醒了一句。

“谢谢,”售票员对小菜笑了笑,接着用中国式英文对着老外说道,“Please buy ticket.”

“Oh ! yes.”老外急忙掏钱包拿了一张十元人民币。

“买票了,买票了,还有两位,不要给不买票的人任何机会……”售票员找了老外钱后吆喝着,又对着一穿着同样公交制服的女的说道,“小姐,请买票!”

“我也是公交公司的,”这女的拿出一个公交证件,在售票员面前晃了晃。

“不好意思,公司早就出规定了,工作证不得作为乘车凭证。”售票员说道。

“我乘车从来就没买过票,凭什么在这就要买票。”这个乘客开始耍赖。

此时旁边的乘客都来劲了,七嘴八舌说起来。

“公交公司的员工就不是乘客呀,国家总理来也要买票的。”

“这人怎么这样,想占大伙的便宜呀。”

“你还当过去呀,现在二十一世纪不吃大锅饭了。欠债还钱,乘车买票,天经地义……”

“行了行了,不就是一张票吗,搞什么搞,呐!买票。”这不想买票的小姐终于扛不住了,递出去两元钱买了票。

“还有哪一位没有买票,请买票。”售票员继续在拥挤的车厢里跋涉着。

“小偷!你这小偷,把手机还我。”突然站在小菜不远处的一小姑娘对着一猥琐的男人叫了起来。

“你不要乱讲,我哪有偷你手机。”

“我看见你刚才把手伸进了我的包里。就是你偷的。”

“我没有偷,你看错了。”

“我明明看见你偷的。”小姑娘急得哭了出来。

小菜看不过去了,“你的手机号多少,我帮你打打看。”

“138xxxx8888”小姑娘像是看到了希望。

“哇,这么强的号,手机一定不会丢。”小菜羡慕着,用自己的手机拨了这个号码。

那人眼看着不对,想往门口跑,小菜和大鸟冲了上去,一把按住他。

“你看,我的手机响了,就在他身上。”小姑娘叫了起来,“就是他,他就是小偷。”

此时两个小伙已经把猥琐男死死按在了地板上。

“快打110报警!”大鸟喊道。

此时公交车也停了下来,所有的乘客都议论着“小偷真可恶”的话题。

不一会,民警来了,问清楚了来由,正准备将小偷带走时,售票员对着小偷发话了:“慢着,你是那个没有买票的人吧?”

“啊?嗯!是的。”小偷一脸沮丧回答道。

“想走?!可以。先买票再说!”售票员干脆地说。小菜和大鸟对望一眼,异口同声道:“强!”

……

2 迭代器模式

“小菜,今天你真见到强人了吧。”大鸟在下车后,对小菜说道。

“这个售票员,实在够强,”小菜学着模仿道,“想走?!可以。先买票再说!”

“这售票员其实在做一件重要的事,就是把车厢里的所有人都遍历了一遍,不放过一个不买票的乘客。这也是一个设计模式的体现。”

“大鸟,你也够强,什么都可以往设计模式上套,这也是模式?”

“当然是模式。这个模式就叫做迭代器模式。”

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。[DP]

“你想呀,售票员才不管你上来的是人还是物〈行李),不管是中国人还是外国人,不管是不是内部员工,甚至哪怕是马上要抓走的小偷,只要是来乘车的乘客,就必须要买票。同样道理,当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。另外,售票员从车头到车尾来售票,也可以从车尾向车头来售票,也就是说,你需要对聚集有多种方式遍历时,可以考虑用迭代器模式。由于不管乘客是什么,售票员的做法始终是相同的,都是从第一个开始,下一个是谁,是否结束,当前售到哪个人了,这些方法每天他都在做,也就是说,为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。

“听你这么一说,好像这个模式也不简单哦。”

“哈,本来这个模式还是有点意思的,不过现今来看迭代器模式实用价值远不如学习价值大了,MartinFlower 甚至在自己的网站上提出撤销此模式。因为现在高级编程语言如C#、JAVA等本身已经把这个模式做在语言中了。”

“哦,是什么?”

“哈,foreach in你熟悉吗?”

“啊,原来是它,没错没错,它就是不需要知道集合对象是什么,就可以遍历所有的对象的循环工具,非常好用。”

“另外还有像IEnumerable接口也是为迭代器模式而准备的。不管如何,学习一下GoF的迭代器模式的基本结构,还是很有学习价值的。研究历史是为了更好地迎接未来。

3 迭代器实现

想走?可以!先买票——迭代器模式
迭代器模式(Iterator)结构图

Iterator迭代器抽象类

/// <summary>
/// 迭代器抽象类,用于定义得到开始对象、得到下一个对象、判断是否到结尾、当前对象等抽象方法,统一接口
/// </summary>
abstract class Iterator
{
  public abstract object First();
  public abstract object Next();
  public abstract bool IsDone();
  public abstract object CurrentItem();
}

Aggregate聚集抽象类

/// <summary>
/// 聚集抽象类
/// </summary>
abstract class Aggregate
{
  /// <summary>
  /// 创建迭代器
  /// </summary>
  /// <returns></returns>
  public abstract Iterator CreateIterator();
}

ConcreteIterator具体迭代器,继承Iterator

class ConcreteIterator : Iterator
{
  // 定义了一个具体聚集对象
  private ConcreteAggregate aggregate;
  private int current = 0;

  /// <summary>
  /// 初始化时将具体的聚集对象传入
  /// </summary>
  /// <param name="aggregate"></param>
  public ConcreteIterator(ConcreteAggregate aggregate)
  {
    this.aggregate = aggregate;
  }

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

  public override object Next()
  {
    object ret = null;

    current++;
    if(current<aggregate.Count)
    {
      ret = aggregate[current];
    }

    return ret;
  }

  public override bool IsDone()
  {
    return current >= aggregate.Count;
  }

  /// <summary>
  /// 返回当前的聚集对象
  /// </summary>
  /// <returns></returns>
  public override object CurrentItem()
  {
    return aggregate[current];
  }
}

ConcreteAggregate具体聚集类,继承Aggregate

/// <summary>
/// 具体聚集类
/// </summary>
class ConcreteAggregate : Aggregate
{
  // 声明一个IList泛型变量,用于存放聚合对象,用ArrayList同样可以实现
  private IList<object> items = new List<object>();

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

  /// <summary>
  /// 返回聚集总个数
  /// </summary>
  public int Count
  {
    get { return items.Count; }
  }

  /// <summary>
  /// 声明一个索引器
  /// </summary>
  /// <param name="index"></param>
  /// <returns></returns>
  public object this[int index]
  {
    get { return items[index]; }
    set { items.Insert(index, value); }
  }
}

客户端代码

static void Main(string[] args)
{
  // 公交车,即聚焦对象
  ConcreteAggregate a = new ConcreteAggregate();

  // 新上来的乘客,即对象数组
  a[0] = "大鸟";
  a[1] = "小菜";
  a[2] = "行李";
  a[3] = "老外";
  a[4] = "公交内部员工";
  a[5] = "小偷";

  // 售票员出场,先看好了上车的是哪些人,即声明了迭代器对象
  Iterator i = new ConcreteIterator(a);

  // 从第一个乘客开始
  object item = i.First();
  while(!i.IsDone())
  {
    // 对面前的乘客告知请买票
    Console.WriteLine($"{i.CurrentItem()} 请买票");

    // 下一乘客
    i.Next();
  }

  Console.Read();
}

运行结果

大鸟 请买票
小菜 请买票
行李 请买票
老外 请买票
公交内部员工 请买票
小偷 请买票

“看到没有,这就是我们的优秀售票员售票——迭代器的整个运作模式。”

“大鸟,你说为什么要用具体的迭代器Concretelterator来实现抽象的 lterator呢?我感觉这里不需要抽象呀,直接访问ConcreteIterator不是更好吗?”

“哈,那是因为刚才有一个迭代器的好处你没注意,当你需要对聚集有多种方式遍历时,可以考虑用迭代器模式,事实上,售票员一定要从车头到车尾这样售票吗?”

“你意思是,他还可以从后向前遍历?”

“当然是可以,你不妨再写一个实现从后往前的具体迭代器类看看。”

“好的。”

class ConcreteIteratorDesc : Iterator
{
  // 定义了一个具体聚集对象
  private ConcreteAggregate aggregate;
  private int current = 0;

  /// <summary>
  /// 初始化时将具体的聚集对象传入
  /// </summary>
  /// <param name="aggregate"></param>
  public ConcreteIteratorDesc(ConcreteAggregate aggregate)
  {
    this.aggregate = aggregate;
    current = aggregate.Count - 1;
  }

  /// <summary>
  /// 得到聚焦的第一个对象
  /// </summary>
  /// <returns></returns>
  public override object First()
  {
    return aggregate[aggregate.Count - 1];
  }

  /// <summary>
  /// 得到聚焦的下一个对象
  /// </summary>
  /// <returns></returns>
  public override object Next()
  {
    object ret = null;

    current--;
    if (current >= 0)
    {
      ret = aggregate[current];
    }

    return ret;
  }

  /// <summary>
  /// 判断当前是否遍历到结尾,到结尾返回true
  /// </summary>
  /// <returns></returns>
  public override bool IsDone()
  {
    return current < 0;
  }

  /// <summary>
  /// 返回当前的聚集对象
  /// </summary>
  /// <returns></returns>
  public override object CurrentItem()
  {
    return aggregate[current];
  }
}

“写得不错,这时你客户端只需要更改一个地方就可以实现反向遍历了”

// Iterator i = new Concretelterator(a);
Iterator i = new ConcreteIteratorDesc(a);

“是呀,其实售票员完全可以更多的方式来遍历乘客,比如从最高的到最矮、从最小到最老、从最靓丽酷毙到最猥琐龌龊。”小菜已经开始头脑风暴。

“神经病,你当是你呀。”大鸟笑骂。

4 .NET的迭代器实现

“刚才我们也说过,实际使用当中是不需要这么麻烦的,因为.NET框架已经为你准备好了相关接口,你只需去实现就好。”

IEumerator支持对非泛型集合的简单迭代接口。

想走?可以!先买票——迭代器模式

IEnumerable公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。

public interface IEnumerable
{
  // 返回一个循环访问集合的枚举数
  IEumerator GetEnumerator();
}

“你会发现,这两个接口,特别是IEumerator要比我们刚才写的抽象类Iterator要简洁,但可实现的功能却一点不少,这其实也是对GoF的设计改良的结果。”

“其实具体类实现这两个接口的代码也差别不大,是吗?”

“是的,区别不大,另外这两个接口还有相应的泛型接口,去查 MSDN帮助就可以了。”

“有了这个基础,你再来看你最熟悉的 foreach in 就很简单了”。

static void Main(string[] args)
{
  // 也可以是ArrayList集合
  IList<string> a = new List<string>();

  a[0] = "大鸟";
  a[1] = "小菜";
  a[2] = "行李";
  a[3] = "老外";
  a[4] = "公交内部员工";
  a[5] = "小偷";
  
  foreach(string item in a)
  {
    Console.WriteLine($"{item} 请买票");
  }

  Console.Read();
}

“这里用到了foreach in而在编译器里做了些什么呢﹖其实它做的是下面的工作。”

IEnumerator<string> e = a.GetEnumerator();
while (e.MoveNext())
{
  Console.WriteLine($"{e.Current} 请买票");
}

“原来foreach in就是实现这两个接口来实际循环遍历呀。”

“是的,尽管我们不需要显式的引用迭代器,但系统本身还是通过迭代器来实现遍历的。总地来说,迭代器(Iterator)模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。迭代器模式在访问数组、集合、列表等数据时,尤其是数据库数据操作时,是非常普遍的应用,但由于它太普遍了,所以各种高级语言都对它进行了封装,所以反而给人感觉此模式本身不太常用了。”

5 迭代高手

“哈哈,看来那个售票员是最了不起的迭代高手,每次有乘客上车他都数数,统计人数,然后再对整车的乘客进行迭代遍历,不放过任何漏网之鱼,啊,应该是逃票之人。”

“隔行如隔山,任何行业都有技巧和经验,需要多思考、多琢磨,才能做到最好的。”

“嗯,编程又何尝不是这样,我相信代码没有最好,只有更好,我要继续努力。”


每天学一点,不贪多。

下一篇我们接着读“第21章 有些类也需计划生育——迭代器模式”,欢迎关注微信公众号【乐趣课堂】。

原文出处:微信公众号【乐趣课堂】

原文链接:

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论