设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

上篇博客我们从醋溜土豆丝与清炒苦瓜中认识了“模板方法模式”,那么在今天这篇博客中我们要从电影院中来认识"迭代器模式"(Iterator Pattern)。“迭代器模式”顾名思义就是通过迭代的形式来取出容器中的值。如果你对Java语言熟悉的话,那么你应该使用过Java中的迭代器,迭代器一般使用hasNext()方法来判断是否有下一个值,如果有下一个值的话,那么就使用next()方法来获取下一个值。本篇博客中就从“电影院”中来认识一下这种“迭代器模式”,并且将数组与字典使用迭代器进行遍历。具体说来使用迭代器的数组与字典对外所展现的遍历方式是一致的,也就是说用户在遍历字典或者数组时,所使用的方法是一致的。当然我们今天的任务是使用Swift语言去实现属于Swift的迭代器,使用该迭代器来遍历字典和数组。

今天博客中所使用的场景与电影院有关,我们假设在一家商场中有两家电影院,而商场中间的大屏幕会滚动展示两家电影院所放映的电影。两家电影院的区别就是其存储电影资源的方式不同,一个是使用数组来存储的电影,一个是使用字典来存储的电影。如果不使用迭代器模式的话,那么两家电影院所遍历电影的方式肯定是不同的。今天博客中我们先给出无“迭代器模式”中遍历两家电影资源的方式,然后在给出“迭代器模式”下遍历两家电影院中电影资源的方式。当然,在下方使用迭代器遍历时,我们也使用了“工厂方法模式”,具体请参见下详细实现。

下方是迭代器模式的定义:

迭代器模式:提供一种方法顺序访问一个聚合对象中的每个元素,而又不暴漏其内部的表示。

一、无“迭代器”的电影院

1.无“迭代器”电影院的类图

在本篇博客中的第一部分我们先给出无“迭代器”的电影院遍历其电影资源的方式。因为不同电影院存储电影资源的方式不同,所以在没有迭代器模式下两者的遍历方式是不同的。在代码设计中我们一贯遵循“依赖接口编程,而不依赖具体实现”的原则,下方虽然没有使用“迭代器模式”,但是我们还是要定义接口的。下方的类图中的CinemaType01接口就是我们所有影院要实现的接口。在该协议中有一个display()方法,用来展示该影院正在放映的电影,而我们的Market(商场)类就是依赖于这个影院接口,在Market的类中要调用具体影院的display()方法来展示放映的电影。要说明一点就是CinemaType01协议是我们商场中给商场中的电影院所制定的规则,因为我们的Market要调用display()方法将商场中的影院所热播的电影投影到大屏幕上,只要是入驻该Market的影院就得遵循CinemaType01协议,并实现display()方法。因为影院存储电影的方式不同所以display()的实现也不同。

通过下方的类图,我们容易看到,Market虽然是调用具体影院的display()方法。但是Market不依赖于影院的具体实现,而是依赖于影院的接口,也就是下方的CinemaType01协议。Cinema01和Cinema01两个类就是我们商场中的两个具体的电影院了。Cinema01中的items是个数组,Cinema02中的是个字典。没有使用“迭代器”的商场影院的整体设计的类图如下所示:

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

2. 代码实现

上面的类图是我们的设计,也就是类似于设计图纸。接下来我们开始搬砖,要对上面的设计图纸进行实现,也就是我们的代码实现。因该示例比较简单,所以在该部分我们实现完毕后会给出相应的测试用例。测用例就相当于盖好的商品房要做样板间一样呢。下方片段就是我们给出的代码实现。

在下方代码片段中,最上方就是我们所有电影院都要实现的协议CinemaType01,在该协议中我们声明了display()方法来展示当前该影院放映的电影。紧接着该协议下方的两个实现就是我们的两个电影院Cinema01和Cinema02。在这两个电影院中一个使用的Array来存储的影片资源,一个使用的是Dictionary来存储的电影资源。Cinema01中的display()所做的事情就是对数组的遍历,而Cinema02所做的事情就是对字典的遍历。而我们的商场类Market就负责调用相应电影院中的display()方法来展示商场中电影院所热播的电影。

在Market类中我们需要注意一下,所有影院是存在cinemas数组中的。而我们在声明cinemas的数组类型时,为该数组的泛型指定的是CinemaType01协议(也就是是接口),这说明cinemas存储的是遵循CinemaType01协议的所有电影院,而不仅仅是这两个电影院。这样如果来了第三家电影院就可以无缝的给我们商场的大屏幕进行对接了。下方就是我们的具体实现。

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

3、测试用例

我们搬完砖盖完房子了,接下来到了测试的时间了,下方就是我们的测试用例。因为该示例比较简单,所以我们的测试用例也是比较简单的,就下边简简单单的几行代码。虽然简单,但是测试用例还是比较简单的。下方我们创建了一个商场对象,并且给该商场对象指定了两家电影院,也就是我们商场中所入驻的电影院。然后我们的商场会调用display()方法来打印所播放的电影,具体如下所示:

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

二、使用“迭代器”来规范商场中的影院

上面的设计似乎没有什么问题,也便于后期的扩充,在来一家影院的话,只要实现商场中定义的CinemaType01规则即可,也就是实现display()方法即可。因为我们的Market要调用这个display()方法。但是每个电影院中存储电影资源的方式不同,导致display()方法各有不同。商场为了规范电影院,提出每个电影院要使用同一个的display()方法。因为这个要求是通过迭代器来实现的,所以我们将电影院中的display()方法进行了一个重命名,我们将其重命名为iteratorItem()。

下面我要引入迭代器,然后就可以将每个电影院中的display()方法进行提取了。我们引入迭代器后将display()方法重命名为iteratorItem()。我们统一影院中的iteratorItem()方法的解决方案是引入迭代器。因为迭代器对外使用的方式是一样的,我们可以为不同的数据类型指定不同的迭代器,比如数组迭代器,字典迭代器。无论是什么迭代器,外部使用该迭代器的方式是一致的,这样就可以做到无论使用什么类型来存储电影资源,只要使用迭代器遍历,其遍历方式是不变的。可能此处说的有些抽象,接下来将会给出具体的设计细节与实现细节,来直观的感受一下迭代器的魅力。有了迭代器我们就可以使用一个display()方法(也就是该部分的iteratorItem()方法)来遍历不同类型的数据了。

1.引入“迭代器”后的电影院

还是老套路,首先我们会给出类图的设计,然后再给出具体实现呢。下方就是我们引入“迭代器”后的类图。大眼一看也许会有些抽象,客官莫着急,听我慢慢道来。在下方类图中大体分为三个模板一个是Market类,这个类与之前没有什么区别。绿框中是我们引入的迭代器,黄框中是我们重构后的电影院,在电影院使用迭代器后,我们在此使用了工厂方法模式,具体请看下方详述。

我们先来看今天的主题,也就是迭代器的设计思路。下方绿框中是我们引入的迭代器。当然我们是依赖接口编程的,迭代器怎么能没有接口呢。在迭代器设计之初我们先给出迭代器的协议,也就是绿框中的Iterator协议。该协议中有两个方法,一个是hasNext(),用来判断是否有下一项。另一个是next()方法,如果有下一项,那么就使用next()方法来获取下一项。在遵循Iterator协议的基础上我们给出了数组迭代器ArrayIterator和字典迭代器DictionaryIterator的实现。在代码实现中我们会给出详细的实现方式。

接着我们看黄框中的部分,也就是我们使用工厂方法模式重构后的电影院。在电影院协议CinemaType中定义了电影院中要实现的方法,其中的iteratorItem()方法就是我们上一部分的display()方法。在引入迭代器后,所有电影院使用迭代器进行遍历元素的方式都是一样的(都是使用hasNext()和next()方法),所有我们将iteratorItem()方法的默认实现放在了CinemaType协议的默认延展中。而createIterator()方法就是我们的工厂方法,它负责创建相应的迭代器。createIterator()方法依赖于迭代器的接口而不依赖于迭代器的具体实现。如果是对数组进行遍历,那么该方法创建的就是数组迭代器,如果是对字典遍历,那么创建的就是字典迭代器。

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

2. 上方类图的具体实现

接下来我们有到了搬砖的时刻了,在该部分将会对上述类图进行具体的代码实现,当然我们使用的仍然是Swift语言。如果你对其他语言使用起来更为得心应手,你就使用你拿手的面向对象语言来实现呢。下方我们会先给出迭代器的实现,然后在给出电影院的实现。Market类的结构基本不变。

(1)、实现我们的迭代器

从上面类图的绿框中我们不难看出,我们要先给出迭代器协议的实现,然后给出数组迭代器和字典迭代器的具体实现。下方代码片段就是对应着上方类图中绿框部分的实现。Iterator协议中的内容比较简单,就是声明了外部使用迭代器的两个方法:hasNext()和next()。

ArrayIterator就是我们实现的数组迭代器。在ArrayIterator中对数组进行了遍历,position记录了下一个元素的下标,hasNext()中所做的事情就是通过position来判断是否有下个元素。next()就是通过position来获取数组中的值。DictionaryIterator就是我们创建的字典迭代器,该迭代器的功能是对字典进行遍历的。其中的position也是用来记录下一个元素下标的,其中的allKeys数组用来获取当前字典的所有key的,allKeys数组通过当前的position来获取key,然后通过key获取该字典相应的值。具体实现方式如下所示:

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

(2)、在影院中使用上述迭代器

接下来就是规范影院的时候到了,商场规定入驻本商城的电影商家只需要选择相应的迭代器即可。遍历的默认实现会在电影院协议的默认延展中给出。下方代码片段就是使用迭代器后的影院实现。

CinemaType协议就是商场规定的影院协议,其中定义了两个方法,createIterator()方法负责来创建特定的迭代器,iteratorItem()方法则负责使用createIterator()创建的迭代器来变量相应的数据。此处的createIterator()方法就是我们“工厂方法模式”中的工厂方法,也就是说在我们设计影院结构时我们使用了“工厂方法模式”。关于工厂方法模式的更多的细节,请参考之前的工厂模式主题的博客《设计模式(四):从“兵工厂”中探索简单工厂、工厂方法和抽象工厂模式》,关于工厂模式在此就不做过多的赘述了。

extension CinemaType就是电影院协议的默认延展。其中给出了iteratorItme()方法的具体实现,该方法适用于所有迭代器的遍历。因为所有迭代器都遵循于Iterator协议,都有hasNext()方法与next()方法,所有所有电影院都可以使用该方法来迭代遍历自己的元素。

Cinema01和Cinema02这两个类则是我们电影院的具体实现。两个类都遵循CinemaType协议,并给出了createIterator()方法。Cinema01使用的是数组来存储的电影资源,所有创建的是数组迭代器。Cinema02使用的是字典存储的电影资源,所以创建的是字典迭代器。无论创建什么样的类型的迭代器,iteratorItme()方法都是可以正常使用的,这也就是使用迭代器的好处。

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

(3)、商场类与测试用例

经过上面的两步,我们已经将迭代器的核心实现完毕。下方的代码就是我们的Market类与测试用例。Market类几乎没有变化,我们之前在Market中调用的是电影院中的display()方法,只不过现在我们是调用电影院的iteratorItem()方法。而Market类的使用方式没有任何的变化,也就是我们的测试用例没有任何的变化。下方就是我们的Market类与测试用例以及输出结果。

设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

至此我们的迭代器模式的完整实例已经实现完毕,其好处就是在于如果商场进入了第三家电影院,只需要遵循相应的协议并指定相应的迭代器即可。至于如何遍历,交给我们的默认实现来做。

今天博客中的代码仍然会在Github上进行分享,分享地址为:https://github.com/lizelu/DesignPatterns-Swift

上一篇:STL 迭代器适配器(iterator adapter)


下一篇:Avalon接口协议