本文讨论迭代器模式,这是一个非常有用且流行的模式,很多语言和框架里都会用到。
问题提出
比如,我们要创建一个Web浏览器。
每一个Web浏览器都有历史记录的概念,我们访问不同的web站点,然后点击“回退”按钮就能返回到历史浏览记录,让我们尝试在代码里实现。
创建BrowserHistory类,它有一个类型为List<String>的私有属性 urls,并有push、pop方法用于urls的操作。
在Main类中,我们保存相关url记录(push操作),但看不到url的历史记录(即 urls的内容),因为urls是私有属性。
可以在BrowserHistory类中创建 urls 的 getter,然后在main函数中遍历urls从而读出url的历史记录。
这里有个问题,如果明天我们决定让BrowserHistory使用不同的数据结构来存储url记录,访问url历史记录的main函数将会出错。比如,我们更改为使用堆栈或固定长度的数组存储url历史记录。
我们发现main函数就崩溃了,因为属性urls在新的数据结构中不存在size方法和get方法。
用遥控器作比喻,老版本的遥控器内部元器件进行了升级,必须不影响用户的使用,用户依然可以在升级后的遥控器上选择节目,调整音量。
应用程序也一样,对象内部数据结构的改变不能影响它的使用者。对于BrowserHistory类,urls的数据结构,以及pop方法、push方法的实现都属于内部,它们的更改不能影响到外部的使用者。
解决方案
BrowseHistory类目前仅有push和pop方法,我们添加一些方法使其支持迭代,调用迭代方法的用户无需了解内部的实现。
我们给BrowseHistory类添加next()、current()、hasNext()方法,这样使用者比如Main类只需调用这些方法就能遍历数据(类似上图的while 循环结构),而不用管BrowseHistory类内部使用了List、Array或Stack来存储数据。
目前的类结构不符合单一职责原则,push和pop方法负责管理url历史记录,而next、current、hasNext方法则负责数据的读取,我们需要将其拆分。这里将迭代读取数据这部分功能移至另一个类,叫Iterator。
很明显,迭代器有很多种,比如List迭代器,Array迭代器,或Stack迭代器等。我们将Iterator抽象成接口,定义了next()、current()、isDone()方法。ListIterator类、ArrayIterator类等实际迭代器实现了Iterator接口。BrowseHistory类则添加一个方法createIterator(),返回Iterator。
这就形成了迭代器模式(Iterator Pattern)
使用迭代器模式,BrowseHistory内部的修改影响就只限制在内部。
我们用代码实现。
代码实现
创建接口(interface) Iterator, 定义hasNext、current和next方法,current方法在我们这个例子返回String类型,但在其他场景可能返回另外类型,我们用泛型T代替String,增加通用性。
迭代接口(interface Iterator)实现类比如ListIterator,设计为嵌套在BrowseHistory类里面,一是为了只影响BrowseHistory内部,二是可以直接使用BrowseHistory类的私有属性urls。ListIterator的作用就是迭代获取urls数据,它持有一个BrowseHistory对象,还需要有一个index属性,表示urls列表的索引位置。
外部已无需了解内部的数据结构,我们删除BrowseHistory的getUrls方法。
再添加createIterator方法,用于返回内部实际使用的迭代器。
完成了BrowseHistory类的修改,再到Main类使用迭代器进行数据获取。
如图,在Main中,我们调用Iterator接口的hasNext,current,next等方法就可以迭代浏览urls的记录,而根本无需了解内部的数据结构。
明天我们改变BrowseHistory类的内部实现,Main的代码不用作任何更改,照样可以浏览urls的记录,我们来看一下。
优点
BrowseHistory类内部的数据结构List改成数组,外部则不会受影响。
我们修改BrowseHistory类内部的List<String>为数组,并相应地修改push、pop方法;再删除原来的ListIterator嵌套迭代器类(已用不到),新增ArrayIterator迭代器类实现Iterator接口用于数组的迭代。
这里所有的修改均只在BrowseHistory类内部,外部的Main不用做任何修改。
小结
迭代器模式非常有用且流行,它可以让外部无需了解内部的结构就能浏览内部的数据,在内部的数据结构发生变化时不影响外部。