寫代碼不少使用數組或者類似的集合對象吧?每次要遍歷一遍數組怎麼辦?For 循環!或者while循環,一個一個訪問每個位置的元素,直到數組末尾。STL裏面甚至有專門的迭代器,針對具體的集合類對象,有對應使用的迭代器。STL的迭代器提供了豐富的遍歷方法,如訪問集合對象的首位元素、末位元素、指定位置的元素、下一個元素……怎麼樣,是不是感覺有了迭代器,遍歷方法不再是難事了?
1.迭代器模式概述
遍歷在日常編碼過程中經常使用,通常是需要對一個具有很多對象實例的集合(稱爲聚合對象)進行訪問或獲取。比如要取聚合對象的首位元素、判斷是否在聚合對象的末尾等。針對聚合對象的遍歷,迭代器模式是一種很有效的解決方案,也是一種使用頻率很高的設計模式。
迭代器模式:
提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露該對象的內部表示。
通過引入迭代器,可以將數據的遍歷功能從聚合對象中分離出來,這樣一來,聚合對象只需負責存儲數據,而迭代器對象負責遍歷數據,使得聚合對象的職責更加單一,符合單一職責原則。
2.迭代器模式結構
迭代器模式結構中包含聚合和迭代器兩個層次的結構。爲方便擴展,迭代器模式常常和工廠方法模式結合。迭代器模式的UML圖如下。有圖可知,迭代器模式有以下幾個角色:
- Iterator(抽象迭代器):聲明瞭訪問和遍歷聚合對象元素的接口,如first()方法用於訪問聚合對象中第一個元素,next()方法用於訪問下一個元素,hasNext()判斷是否還有下一個元素,currentItem()方法用於獲取當前元素。
- ConcreteIterator(具體迭代器):實現抽象迭代器聲明的方法,通常具體迭代器中會專門用一個變量(稱爲遊標)來記錄迭代器在聚合對象中所處的位置。
- Aggregate(抽象聚合類):用於存儲和管理元素對象,聲明一個創建迭代器的接口,其實是一個抽象迭代器工廠的角色。
- ConcreteAggregate(具體聚合類):實現了方法createIterator(),該方法返回一個與該具體聚合類對應的具體迭代器ConcreteIterator的實例。
3.迭代器模式代碼實例
電視機遙控器是迭代器的一個現實應用,通過它可以實現對電視頻道集合的遍歷操作,電視機可以看成一個存儲頻道的聚合對象。本例Jungle將採用迭代器模式來模擬遙控器操作電視頻道的過程。
很明顯,遙控器是一個具體的迭代器,具有上一個頻道previous() 、下一個頻道next()、當前頻道currentChannel()等功能;需要遍歷的聚合對象是電視頻道的集合,即電視機。本例的UML圖如下:
3.1.抽象聚合類和具體聚合類
#ifndef __AGGREGATE_H__
#define __AGGREGATE_H__
#include <vector>
using namespace std;
// 前向聲明,因爲兩個類互相引用
class Iterator;
class RemoteControl;
// 抽象聚合類 Aggregate
class Aggregate
{
public:
Aggregate(){}
virtual Iterator* createIterator() = 0;
};
// 具體聚合類 Television
class Television :public Aggregate
{
public:
Television();
Television(vector<string> iChannelList);
// 實現創建迭代器
Iterator* createIterator();
// 獲取總的頻道數目
int getTotalChannelNum();
void play(int i);
private:
vector<string> channelList;
};
#endif //__AGGREGATE_H__
實現:
#include "Iterator.h"
Television::Television(){}
Television::Television(vector<string> iChannelList){
this->channelList = iChannelList;
}
Iterator* Television::createIterator(){
RemoteControl *it = new RemoteControl();
it->setTV(this);
return (Iterator*)it;
}
int Television::getTotalChannelNum(){
return channelList.size();
}
void Television::play(int i){
printf("現在播放:%s……\n", channelList[i].c_str());
}
3.2.抽象迭代器
// 抽象迭代器
class Iterator
{
public:
Iterator(){}
// 聲明抽象遍歷方法
virtual void first() = 0;
virtual void last() = 0;
virtual void next() = 0;
virtual void previous() = 0;
virtual bool hasNext() = 0;
virtual bool hasPrevious() = 0;
virtual void currentChannel() = 0;
};
3.3.具體迭代器:RemoteControl
// 遙控器:具體迭代器
class RemoteControl :public Iterator
{
public:
RemoteControl(){}
void setTV(Television *iTv){
this->tv = iTv;
cursor = -1;
totalNum = tv->getTotalChannelNum();
}
// 實現各個遍歷方法
void first(){
cursor = 0;
}
void last(){
cursor = totalNum - 1;
}
void next(){
cursor++;
}
void previous(){
cursor--;
}
bool hasNext(){
return !(cursor == totalNum);
}
bool hasPrevious(){
return !(cursor == -1);
}
void currentChannel(){
tv->play(cursor);
}
private:
// 遊標
int cursor;
// 總的頻道數目
int totalNum;
// 電視
Television* tv;
};
3.4.客戶端代碼示例及結果
#include <iostream>
#include "Iterator.h"
int main()
{
vector<string> channelList = { "新聞頻道", "財經頻道", "體育頻道", "電影頻道", "音樂頻道", "農業頻道", "四川衛視", "成都衛視" };
// 創建電視
Television *tv = new Television(channelList);
// 創建遙控器
Iterator *remoteControl = tv->createIterator();
// 順序遍歷
printf("順序遍歷:\n");
remoteControl->first();
// 遍歷電視所有頻道
while (remoteControl->hasNext()){
remoteControl->currentChannel();
remoteControl->next();
}
printf("\n\n");
// 逆序遍歷
printf("逆序遍歷:\n");
remoteControl->last();
// 遍歷電視所有頻道
while (remoteControl->hasPrevious()){
remoteControl->currentChannel();
remoteControl->previous();
}
printf("\n\n");
system("pause");
return 0;
}
結果如下圖:
4.總結
觀察上述代碼可發現,迭代器類和聚合類存在相互包含相互引用的關係,因此代碼裏需要前向聲明某個類(具體操作見上,代碼資源見https://github.com/FengJungle/DesignPattern)。
優點:
- 支持以不同的方式遍歷一個聚合對象,在同一個聚合對象上可以定義多個遍歷方式。
- 簡化了聚合類,使得聚合類的職責更加單一;
- 迭代器模式中引入抽象層,易於增加新的迭代器類,便於擴展,符合開閉原則。
缺點:
- 將聚合類中存儲對象和管理對象的職責分離,增加新的聚合類時同樣需要考慮增加對應的新的迭代器類,類的個數成對增加,不利於系統管理和維護;
- 設計難度較大,需要充分考慮將來系統的擴展。
適用環境:
以下場景可以考慮使用迭代器模式:
- 訪問一個聚合對象而無需暴露它的內部結構;
- 需要爲一個聚合對象提供多種遍歷方法。
歡迎關注知乎專欄:Jungle是一個用Qt的工業Robot
歡迎關注Jungle的微信公衆號:Jungle筆記