前言
最近非常感傷,總是懷念大學的日子,做夢的時候也常常夢到。夢到大學在電腦前傻傻的敲着鍵盤,寫着代碼,對付着數據結構與算法的作業;建立一個鏈表,遍歷鏈表,打印鏈表。現在把那個時候聲明的鏈表的頭文件拿出來看看:
1 typedef struct tagNode 2 { 3 int value; 4 tagNode *pPre; 5 tagNode *pNext; 6 }Node; 7 8 class CList 9 { 10 public: 11 CList(); 12 CList(size_t n); 13 ~CList(); 14 15 bool PushBack(int value); 16 bool PopBack(int &value); 17 bool Insert(int pos, int value); 18 bool Delete(int pos); 19 bool IsEmpty(); 20 int GetLength(); 21 22 void Print(); 23 24 // To iterate the list 25 bool HasNext(); 26 int Next(); 27 28 private: 29 int m_iLength; 30 Node *m_pCurrent; 31 Node *m_pHead; 32 Node *m_pTail; 33 };
再回頭看看,自己寫的代碼都有點不認識了。是的,那個時候,就是直接將鏈表的創建和遍歷都放在一類中,就是為了方便,直到那天看了迭代器設計模式,讓我有了一次回過頭來重新審視自己寫過的代碼,認識自己的不足的機會。
迭代器模式
在GOF的《設計模式:可復用面向對象軟件的基礎》一書中對迭代器模式是這樣說的:提供一種方法順序訪問一個聚合對象中各個元素,而又不需要暴露該對象的內部表示。
一個聚合對象,就是所謂的對象容器了;作為一個容器,都應該提供一種方法來讓別人可以訪問它的元素;但是,有的時候,我是不希望遍歷容器的人知道我的容器是如何實現的;那該怎么辦?就像我在大學那樣實現的鏈表,只提供了從頭到尾的遍歷,如果我需要從尾到頭的遍歷呢?是不是我又要添加對應的方法了呢!!!容器的遍歷方式千變萬化,我們不知道需求是如何的,如果需求變了,那么我們的代碼就會發生很大的改動,所以,我們需要去改變;對於上面的代碼,當我對同一個鏈表對象進行多次遍歷時,是不是就出現了m_pCurrent對象混亂的局面呢?是的,這一切的一切,都說明,我們必須去將一個容器的內部結構與它的遍歷進行解耦,要是出現上面的情況時,我們就無法面對。就好比STL中的容器,它將容器中對象的實現和遍歷很好的解耦了,所以,我們就無法知道它的內部是如何組織對象數據的,同時,我們也可以按照我們自己的想法去遍歷容器,而不會出現任何差錯。在我們的項目中使用迭代器模式就能很好的將容器對象的內部表示與對它的遍歷進行解耦。接下來,我們再來詳細的總結迭代器模式。
UML類圖

Iterator:定義迭代器訪問和遍歷元素的接口;
ConcreteIterator:實現具體的迭代器;
Aggregate:定義的容器,創建相應迭代器對象的接口;
ConcreteAggregate:具體的容器實現創建相應迭代器的接口,該操作返回ConcreteIterator的一個適當的實例。
使用場合
- 訪問一個聚合對象的內容而無需暴露它的內部表示;
- 支持對聚合對象的多種遍歷(從前到后,從后到前);
- 為遍歷不同的聚合結構提供一個統一的接口,即支持多態迭代。
作用
- 它支持以不同的方式遍歷一個聚合,甚至都可以自己定義迭代器的子類以支持新的遍歷;
- 迭代器簡化了聚合的接口,有了迭代器的遍歷接口,聚合本身就不再需要類似的遍歷接口了。這樣就簡化了聚合的接口;
- 在同一個聚合上可以有多個遍歷,每個迭代器保持它自己的遍歷狀態;因此,我們可以同時進行多個遍歷。
代碼實現
1 #include <iostream> 2 using namespace std; 3 4 typedef struct tagNode 5 { 6 int value; 7 tagNode *pNext; 8 }Node; 9 10 class JTList 11 { 12 public: 13 JTList() : m_pHead(NULL), m_pTail(NULL){}; 14 JTList(const JTList &); 15 ~JTList(); 16 JTList &operator=(const JTList &); 17 18 long GetCount() const; 19 Node *Get(const long index) const; 20 Node *First() const; 21 Node *Last() const; 22 bool Includes(const int &) const; 23 24 void Append(const int &); 25 void Remove(Node *pNode); 26 void RemoveAll(); 27 28 private: 29 Node *m_pHead; 30 Node *m_pTail; 31 long m_lCount; 32 }; 33 34 class Iterator 35 { 36 public: 37 virtual void First() = 0; 38 virtual void Next() = 0; 39 virtual bool IsDone() const = 0; 40 virtual Node *CurrentItem() const = 0; 41 }; 42 43 class JTListIterator : public Iterator 44 { 45 public: 46 JTListIterator(JTList *pList) : m_pJTList(pList), m_pCurrent(NULL){} 47 48 virtual void First(); 49 virtual void Next(); 50 virtual bool IsDone() const; 51 virtual Node *CurrentItem() const; 52 53 private: 54 JTList *m_pJTList; 55 Node *m_pCurrent; 56 }; 57 58 JTList::~JTList() 59 { 60 Node *pCurrent = m_pHead; 61 Node *pNextNode = NULL; 62 while (pCurrent) 63 { 64 pNextNode = pCurrent->pNext; 65 delete pCurrent; 66 pCurrent = pNextNode; 67 } 68 } 69 70 long JTList::GetCount()const 71 { 72 return m_lCount; 73 } 74 75 Node *JTList::Get(const long index) const 76 { 77 // The min index is 0, max index is count - 1 78 if (index > m_lCount - 1 || index < 0) 79 { 80 return NULL; 81 } 82 83 int iPosTemp = 0; 84 Node *pNodeTemp = m_pHead; 85 while (pNodeTemp) 86 { 87 if (index == iPosTemp++) 88 { 89 return pNodeTemp; 90 } 91 pNodeTemp = pNodeTemp->pNext; 92 } 93 return NULL; 94 } 95 96 Node *JTList::First() const 97 { 98 return m_pHead; 99 } 100 101 Node *JTList::Last() const 102 { 103 return m_pTail; 104 } 105 106 bool JTList::Includes(const int &value) const 107 { 108 Node *pNodeTemp = m_pHead; 109 while (pNodeTemp) 110 { 111 if (value == pNodeTemp->value) 112 { 113 return true; 114 } 115 pNodeTemp = pNodeTemp->pNext; 116 } 117 return false; 118 } 119 120 void JTList::Append(const int &value) 121 { 122 // Create the new node 123 Node *pInsertNode = new Node; 124 pInsertNode->value = value; 125 pInsertNode->pNext = NULL; 126 127 // This list is empty 128 if (m_pHead == NULL) 129 { 130 m_pHead = m_pTail = pInsertNode; 131 } 132 else 133 { 134 m_pTail->pNext = pInsertNode; 135 m_pTail = pInsertNode; 136 } 137 ++m_lCount; 138 } 139 140 void JTList::Remove(Node *pNode) 141 { 142 if (pNode == NULL || m_pHead == NULL || m_pTail == NULL) 143 { 144 return; 145 } 146 147 if (pNode == m_pHead) // If the deleting node is head node 148 { 149 Node *pNewHead = m_pHead->pNext; 150 m_pHead = pNewHead; 151 } 152 else 153 { 154 // To get the deleting node's previous node 155 Node *pPreviousNode = NULL; 156 Node *pCurrentNode = m_pHead; 157 while (pCurrentNode) 158 { 159 pPreviousNode = pCurrentNode; 160 pCurrentNode = pCurrentNode->pNext; 161 if (pCurrentNode == pNode) 162 { 163 break; 164 } 165 } 166 167 // To get the deleting node's next node 168 Node *pNextNode = pNode->pNext; 169 170 // If pNextNode is NULL, it means the deleting node is the tail node, we should change the m_pTail pointer 171 if (pNextNode == NULL) 172 { 173 m_pTail = pPreviousNode; 174 } 175 176 // Relink the list 177 pPreviousNode->pNext = pNextNode; 178 } 179 180 // Delete the node 181 delete pNode; 182 pNode = NULL; 183 --m_lCount; 184 } 185 186 void JTList::RemoveAll() 187 { 188 delete this; 189 } 190 191 void JTListIterator::First() 192 { 193 m_pCurrent = m_pJTList->First(); 194 } 195 196 void JTListIterator::Next() 197 { 198 m_pCurrent = m_pCurrent->pNext; 199 } 200 201 bool JTListIterator::IsDone() const 202 { 203 return m_pCurrent == m_pJTList->Last()->pNext; 204 } 205 206 Node *JTListIterator::CurrentItem() const 207 { 208 return m_pCurrent; 209 } 210 211 int main() 212 { 213 JTList *pJTList = new JTList; 214 pJTList->Append(10); 215 pJTList->Append(20); 216 pJTList->Append(30); 217 pJTList->Append(40); 218 pJTList->Append(50); 219 pJTList->Append(60); 220 pJTList->Append(70); 221 pJTList->Append(80); 222 pJTList->Append(90); 223 pJTList->Append(100); 224 225 Iterator *pIterator = new JTListIterator(pJTList); 226 227 // Print the list by JTListIterator 228 for (pIterator->First(); !pIterator->IsDone(); pIterator->Next()) 229 { 230 cout<<pIterator->CurrentItem()->value<<"->"; 231 } 232 cout<<"NULL"<<endl; 233 234 // Test for removing 235 Node *pDeleteNode = NULL; 236 for (pIterator->First(); !pIterator->IsDone(); pIterator->Next()) 237 { 238 pDeleteNode = pIterator->CurrentItem(); 239 if (pDeleteNode->value == 100) 240 { 241 pJTList->Remove(pDeleteNode); 242 break; 243 } 244 } 245 246 // Print the list by JTListIterator 247 for (pIterator->First(); !pIterator->IsDone(); pIterator->Next()) 248 { 249 cout<<pIterator->CurrentItem()->value<<"->"; 250 } 251 cout<<"NULL"<<endl; 252 253 delete pIterator; 254 delete pJTList; 255 256 return 0; 257 }
代碼中實現了一個單向鏈表,將鏈表與迭代器解耦。對於多態迭代,添加抽象類AbstractJTList,聲明如下:
class AbstractJTList { public: virtual Iterator *GetIterator() const = 0; };
Iterator *JTList::GetIterator() const { return new JTListIterator(this); }
好了,這樣的話,在客戶端就不用去new JTListIterator了,只需要這樣:
Iterator *pIterator = pJTList->GetIterator();
這就完全好了;但是,這樣又出現另外一個問題,我在GetIterator中new了一個JTListIterator,對於客戶端來說,我並不知道這個new操作的存在,就會出現客戶端不會去釋放這個new開辟的內存,那么如何實現這個內存的自動釋放呢。好了,就結合迭代器模式,再將之前總結的RAII機制再實際運用一次。
根據RAII機制,需要將這個迭代器進行封裝,讓它具有自動釋放的功能,就得借助另一個類,如下:
class IteratorPtr { public: IteratorPtr(Iterator *pIterator) : m_pIterator(pIterator){} ~IteratorPtr() { delete m_pIterator; } Iterator *operator->(){ return m_pIterator; } Iterator &operator*() { return *m_pIterator; } private: IteratorPtr(const IteratorPtr &); IteratorPtr &operator=(const IteratorPtr &); void *operator new(size_t size); void operator delete(void *); private: Iterator *m_pIterator; };
我們在使用的時候,就像下面這樣:
IteratorPtr pIterator(pJTList->GetIterator());
這樣就省去了釋放迭代器的麻煩了。
總結
迭代器模式是一個很經典的模式。但是,就是因為它太經典了,如果每次都要程序員去重復造輪子,就有點說不過去了,所以,現在基本成型的類庫,都非常好的實現了迭代器模式,在使用這些類庫提供的容器時,並不需要我們親自去實現對應的迭代器;就好比STL了。但是話又說回來了,如此經典的東西,你不去學習是不是很可惜啊;是吧,在當今社會,技多不壓身。好了,永遠記住,設計模式是一種思想,並不是一層不變的,一種思想,你懂的。
