引子:公車售票員#
公車上人潮擁擠,售票員要求每位乘客買票:
- 帶大件行李的乘客 → 行李也要補票
- 不會中文的老外 → 售票員擠出英文「Please buy ticket」
- 公車公司的同事 → 工作證不算乘車憑證,照樣買票
- 抓到的小偷 → 「想走?可以,先買票再說!」
售票員不管乘客是誰、是人是物、是內部員工還是小偷,只要在車上就要買票——他逐一遍歷整車人,不放過任何一位。
這就是迭代器模式的生活原型。
迭代器模式#
迭代器模式(Iterator Pattern):提供一種方法順序訪問一個聚合物件中各個元素,而又不暴露該物件的內部表示。[DP]
適用情境:
- 需要訪問一個聚合物件,不論這些物件是什麼都需要遍歷
- 需要對聚合多種方式遍歷(從車頭到車尾、或從車尾到車頭)
- 為遍歷不同的聚合結構提供統一的介面:開始、下一個、是否結束、當前項
Martin Fowler 甚至在其網站上提議撤銷此模式——因為現在的高級語言(C#、Java)已經把迭代器內建到語言裡了。
不過學習它的歷史與設計思想,仍有價值。
結構#
- Iterator(迭代抽象類):定義
First()、Next()、IsDone()、CurrentItem()等抽象方法,統一介面 - Aggregate(聚合抽象類):宣告
CreateIterator()來建立迭代器 - ConcreteIterator:繼承 Iterator,實作具體迭代邏輯
- ConcreteAggregate:繼承 Aggregate,回傳對應的具體迭代器
classDiagram
class Aggregate {
<<abstract>>
+CreateIterator() Iterator
}
class ConcreteAggregate
class Iterator {
<<abstract>>
+First()
+Next()
+IsDone() bool
+CurrentItem()
}
class ConcreteIterator
Aggregate <|-- ConcreteAggregate
Iterator <|-- ConcreteIterator
ConcreteAggregate ..> ConcreteIterator : creates
ConcreteIterator ..> ConcreteAggregateabstract class Iterator
{
public abstract object First();
public abstract object Next();
public abstract bool IsDone();
public abstract object CurrentItem();
}
abstract class Aggregate
{
public abstract Iterator CreateIterator();
}
class ConcreteIterator : Iterator
{
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIterator(ConcreteAggregate aggregate) { this.aggregate = aggregate; }
public override object First() => aggregate[0];
public override object Next()
{
current++;
return current < aggregate.Count ? aggregate[current] : null;
}
public override bool IsDone() => current >= aggregate.Count;
public override object CurrentItem() => aggregate[current];
}
class ConcreteAggregate : Aggregate
{
private IList<object> items = new List<object>();
public override Iterator CreateIterator() => new ConcreteIterator(this);
public int Count => items.Count;
public object this[int index]
{
get => items[index];
set => items.Insert(index, value);
}
}客戶端:
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();
}多種遍歷方式#
迭代器抽象的價值在於支援多種遍歷方式——售票員可以從車頭走到車尾,也可以從車尾走到車頭:
class ConcreteIteratorDesc : Iterator
{
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIteratorDesc(ConcreteAggregate aggregate)
{
this.aggregate = aggregate;
current = aggregate.Count - 1;
}
public override object First() => aggregate[aggregate.Count - 1];
public override object Next()
{
current--;
return current >= 0 ? aggregate[current] : null;
}
public override bool IsDone() => current < 0;
public override object CurrentItem() => aggregate[current];
}客戶端只要把 new ConcreteIterator(a) 換成 new ConcreteIteratorDesc(a) 即可。
.NET 的迭代器實作#
.NET 已將迭代器整合到語言層級,提供
IEnumerable與IEnumerator介面。
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
public interface IEnumerable
{
IEnumerator GetEnumerator();
}foreach in 是語法糖:
IList<string> a = new List<string> { "大鳥", "小菜", "行李", "老外", "公車內部員工", "小偷" };
foreach (string item in a)
{
Console.WriteLine($"{item} 請買車票!");
}實際上編譯器會展開為:
IEnumerator<string> e = a.GetEnumerator();
while (e.MoveNext())
{
Console.WriteLine($"{e.Current} 請買車票!");
}本章小結#
迭代器模式分離了集合物件的遍歷行為,抽象出一個迭代器類來負責:
- 不暴露集合的內部結構
- 又可讓外部程式碼透明地訪問集合內部的資料
在訪問陣列、集合、列表,特別是資料庫資料操作時,迭代器模式是極其普遍的應用——但正因為太普遍,各種高階語言都做了封裝,反而給人「此模式本身不太常用」的錯覺。