前言
最近非常感傷,總是懷念大學的日子,做夢的時候也常常夢到。夢到大學在電腦前傻傻的敲着鍵盤,寫着代碼,對付着資料結構與算法的作業;建立一個連結清單,周遊連結清單,列印連結清單。現在把那個時候聲明的連結清單的頭檔案拿出來看看:
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;
};
類JTList繼承該抽象類,并實作GetIterator,如下:
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了。但是話又說回來了,如此經典的東西,你不去學習是不是很可惜啊;是吧,在當今社會,技多不壓身。好了,永遠記住,設計模式是一種思想,并不是一層不變的,一種思想,你懂的。