C++設(shè)計(jì)模式之迭代器模式
前言
又到年底了,時(shí)間真的過的好快啊。最近也非常感傷,總是懷念大學(xué)的日子,做夢(mèng)的時(shí)候也常常夢(mèng)到。夢(mèng)到大學(xué)在電腦前傻傻的敲著鍵盤,寫著代碼,對(duì)付著數(shù)據(jù)結(jié)構(gòu)與算法的作業(yè);建立一個(gè)鏈表,遍歷鏈表,打印鏈表。現(xiàn)在把那個(gè)時(shí)候聲明的鏈表的頭文件拿出來看看:
typedef struct tagNode
{
int value;
tagNode *pPre;
tagNode *pNext;
}Node;
class CList
{
public:
CList();
CList(size_t n);
~CList();
bool PushBack(int value);
bool PopBack(int &value);
bool Insert(int pos, int value);
bool Delete(int pos);
bool IsEmpty();
int GetLength();
void Print();
// To iterate the list
bool HasNext();
int Next();
private:
int m_iLength;
Node *m_pCurrent;
Node *m_pHead;
Node *m_pTail;
};
再回頭看看,自己寫的代碼都有點(diǎn)不認(rèn)識(shí)了。是的,那個(gè)時(shí)候,就是直接將鏈表的創(chuàng)建和遍歷都放在一類中,就是為了方便,直到那天看了迭代器設(shè)計(jì)模式,讓我有了一次回過頭來重新審視自己寫過的代碼,認(rèn)識(shí)自己的不足的機(jī)會(huì)。
迭代器模式
在GOF的《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書中對(duì)迭代器模式是這樣說的:提供一種方法順序訪問一個(gè)聚合對(duì)象中各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部表示。
一個(gè)聚合對(duì)象,就是所謂的對(duì)象容器了;作為一個(gè)容器,都應(yīng)該提供一種方法來讓別人可以訪問它的元素;但是,有的時(shí)候,我是不希望遍歷容器的人知道我的容器是如何實(shí)現(xiàn)的;那該怎么辦?就像我在大學(xué)那樣實(shí)現(xiàn)的鏈表,只提供了從頭到尾的遍歷,如果我需要從尾到頭的遍歷呢?是不是我又要添加對(duì)應(yīng)的方法了呢?。。∪萜鞯谋闅v方式千變?nèi)f化,我們不知道需求是如何的,如果需求變了,那么我們的代碼就會(huì)發(fā)生很大的改動(dòng),所以,我們需要去改變;對(duì)于上面的代碼,當(dāng)我對(duì)同一個(gè)鏈表對(duì)象進(jìn)行多次遍歷時(shí),是不是就出現(xiàn)了m_pCurrent對(duì)象混亂的局面呢?是的,這一切的一切,都說明,我們必須去將一個(gè)容器的內(nèi)部結(jié)構(gòu)與它的遍歷進(jìn)行解耦,要是出現(xiàn)上面的情況時(shí),我們就無法面對(duì)。就好比STL中的容器,它將容器中對(duì)象的實(shí)現(xiàn)和遍歷很好的解耦了,所以,我們就無法知道它的內(nèi)部是如何組織對(duì)象數(shù)據(jù)的,同時(shí),我們也可以按照我們自己的想法去遍歷容器,而不會(huì)出現(xiàn)任何差錯(cuò)。在我們的項(xiàng)目中使用迭代器模式就能很好的將容器對(duì)象的內(nèi)部表示與對(duì)它的遍歷進(jìn)行解耦。接下來,我們?cè)賮碓敿?xì)的總結(jié)迭代器模式。
UML類圖
Iterator:定義迭代器訪問和遍歷元素的接口;
ConcreteIterator:實(shí)現(xiàn)具體的迭代器;
Aggregate:定義的容器,創(chuàng)建相應(yīng)迭代器對(duì)象的接口;
ConcreteAggregate:具體的容器實(shí)現(xiàn)創(chuàng)建相應(yīng)迭代器的接口,該操作返回ConcreteIterator的一個(gè)適當(dāng)?shù)膶?shí)例。
使用場合
1.訪問一個(gè)聚合對(duì)象的內(nèi)容而無需暴露它的內(nèi)部表示;
2.支持對(duì)聚合對(duì)象的多種遍歷(從前到后,從后到前);
3.為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口,即支持多態(tài)迭代。
作用
1.它支持以不同的方式遍歷一個(gè)聚合,甚至都可以自己定義迭代器的子類以支持新的遍歷;
2.迭代器簡化了聚合的接口,有了迭代器的遍歷接口,聚合本身就不再需要類似的遍歷接口了。這樣就簡化了聚合的接口;
3.在同一個(gè)聚合上可以有多個(gè)遍歷,每個(gè)迭代器保持它自己的遍歷狀態(tài);因此,我們可以同時(shí)進(jìn)行多個(gè)遍歷。
代碼實(shí)現(xiàn)
#include <iostream>
using namespace std;
typedef struct tagNode
{
int value;
tagNode *pNext;
}Node;
class JTList
{
public:
JTList() : m_pHead(NULL), m_pTail(NULL){};
JTList(const JTList &);
~JTList();
JTList &operator=(const JTList &);
long GetCount() const;
Node *Get(const long index) const;
Node *First() const;
Node *Last() const;
bool Includes(const int &) const;
void Append(const int &);
void Remove(Node *pNode);
void RemoveAll();
private:
Node *m_pHead;
Node *m_pTail;
long m_lCount;
};
class Iterator
{
public:
virtual void First() = 0;
virtual void Next() = 0;
virtual bool IsDone() const = 0;
virtual Node *CurrentItem() const = 0;
};
class JTListIterator : public Iterator
{
public:
JTListIterator(JTList *pList) : m_pJTList(pList), m_pCurrent(NULL){}
virtual void First();
virtual void Next();
virtual bool IsDone() const;
virtual Node *CurrentItem() const;
private:
JTList *m_pJTList;
Node *m_pCurrent;
};
JTList::~JTList()
{
Node *pCurrent = m_pHead;
Node *pNextNode = NULL;
while (pCurrent)
{
pNextNode = pCurrent->pNext;
delete pCurrent;
pCurrent = pNextNode;
}
}
long JTList::GetCount()const
{
return m_lCount;
}
Node *JTList::Get(const long index) const
{
// The min index is 0, max index is count - 1
if (index > m_lCount - 1 || index < 0)
{
return NULL;
}
int iPosTemp = 0;
Node *pNodeTemp = m_pHead;
while (pNodeTemp)
{
if (index == iPosTemp++)
{
return pNodeTemp;
}
pNodeTemp = pNodeTemp->pNext;
}
return NULL;
}
Node *JTList::First() const
{
return m_pHead;
}
Node *JTList::Last() const
{
return m_pTail;
}
bool JTList::Includes(const int &value) const
{
Node *pNodeTemp = m_pHead;
while (pNodeTemp)
{
if (value == pNodeTemp->value)
{
return true;
}
pNodeTemp = pNodeTemp->pNext;
}
return false;
}
void JTList::Append(const int &value)
{
// Create the new node
Node *pInsertNode = new Node;
pInsertNode->value = value;
pInsertNode->pNext = NULL;
// This list is empty
if (m_pHead == NULL)
{
m_pHead = m_pTail = pInsertNode;
}
else
{
m_pTail->pNext = pInsertNode;
m_pTail = pInsertNode;
}
++m_lCount;
}
void JTList::Remove(Node *pNode)
{
if (pNode == NULL || m_pHead == NULL || m_pTail == NULL)
{
return;
}
if (pNode == m_pHead) // If the deleting node is head node
{
Node *pNewHead = m_pHead->pNext;
m_pHead = pNewHead;
}
else
{
// To get the deleting node's previous node
Node *pPreviousNode = NULL;
Node *pCurrentNode = m_pHead;
while (pCurrentNode)
{
pPreviousNode = pCurrentNode;
pCurrentNode = pCurrentNode->pNext;
if (pCurrentNode == pNode)
{
break;
}
}
// To get the deleting node's next node
Node *pNextNode = pNode->pNext;
// If pNextNode is NULL, it means the deleting node is the tail node, we should change the m_pTail pointer
if (pNextNode == NULL)
{
m_pTail = pPreviousNode;
}
// Relink the list
pPreviousNode->pNext = pNextNode;
}
// Delete the node
delete pNode;
pNode = NULL;
--m_lCount;
}
void JTList::RemoveAll()
{
delete this;
}
void JTListIterator::First()
{
m_pCurrent = m_pJTList->First();
}
void JTListIterator::Next()
{
m_pCurrent = m_pCurrent->pNext;
}
bool JTListIterator::IsDone() const
{
return m_pCurrent == m_pJTList->Last()->pNext;
}
Node *JTListIterator::CurrentItem() const
{
return m_pCurrent;
}
int main()
{
JTList *pJTList = new JTList;
pJTList->Append(10);
pJTList->Append(20);
pJTList->Append(30);
pJTList->Append(40);
pJTList->Append(50);
pJTList->Append(60);
pJTList->Append(70);
pJTList->Append(80);
pJTList->Append(90);
pJTList->Append(100);
Iterator *pIterator = new JTListIterator(pJTList);
// Print the list by JTListIterator
for (pIterator->First(); !pIterator->IsDone(); pIterator->Next())
{
cout<<pIterator->CurrentItem()->value<<"->";
}
cout<<"NULL"<<endl;
// Test for removing
Node *pDeleteNode = NULL;
for (pIterator->First(); !pIterator->IsDone(); pIterator->Next())
{
pDeleteNode = pIterator->CurrentItem();
if (pDeleteNode->value == 100)
{
pJTList->Remove(pDeleteNode);
break;
}
}
// Print the list by JTListIterator
for (pIterator->First(); !pIterator->IsDone(); pIterator->Next())
{
cout<<pIterator->CurrentItem()->value<<"->";
}
cout<<"NULL"<<endl;
delete pIterator;
delete pJTList;
return 0;
}
代碼中實(shí)現(xiàn)了一個(gè)單向鏈表,將鏈表與迭代器解耦。對(duì)于多態(tài)迭代,添加抽象類AbstractJTList,聲明如下:
class AbstractJTList
{
public:
virtual Iterator *GetIterator() const = 0;
};
類JTList繼承該抽象類,并實(shí)現(xiàn)GetIterator,如下:
Iterator *JTList::GetIterator() const
{
return new JTListIterator(this);
}
好了,這樣的話,在客戶端就不用去new JTListIterator了,只需要這樣:
Iterator *pIterator = pJTList->GetIterator();
這就完全好了;但是,這樣又出現(xiàn)另外一個(gè)問題,我在GetIterator中new了一個(gè)JTListIterator,對(duì)于客戶端來說,我并不知道這個(gè)new操作的存在,就會(huì)出現(xiàn)客戶端不會(huì)去釋放這個(gè)new開辟的內(nèi)存,那么如何實(shí)現(xiàn)這個(gè)內(nèi)存的自動(dòng)釋放呢。好了,就結(jié)合迭代器模式,再將之前總結(jié)的RAII機(jī)制再實(shí)際運(yùn)用一次。
根據(jù)RAII機(jī)制,需要將這個(gè)迭代器進(jìn)行封裝,讓它具有自動(dòng)釋放的功能,就得借助另一個(gè)類,如下:
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;
};
我們?cè)谑褂玫臅r(shí)候,就像下面這樣:
IteratorPtr pIterator(pJTList->GetIterator());
這樣就省去了釋放迭代器的麻煩了。這里一共涉及了三個(gè)DEMO工程,提供完整DEMO工程下載。(工程下載)
總結(jié)
迭代器模式是一個(gè)很經(jīng)典的模式。但是,就是因?yàn)樗?jīng)典了,如果每次都要程序員去重復(fù)造輪子,就有點(diǎn)說不過去了,所以,現(xiàn)在基本成型的類庫,都非常好的實(shí)現(xiàn)了迭代器模式,在使用這些類庫提供的容器時(shí),并不需要我們親自去實(shí)現(xiàn)對(duì)應(yīng)的迭代器;就好比STL了。但是話又說回來了,如此經(jīng)典的東西,你不去學(xué)習(xí)是不是很可惜啊;是吧,在當(dāng)今社會(huì),技多不壓身。好了,永遠(yuǎn)記住,設(shè)計(jì)模式是一種思想,并不是一層不變的,一種思想,你懂的。
您可能感興趣的文章
- 04-02c語言沒有round函數(shù) round c語言
- 01-10深入理解C++中常見的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10APUE筆記之:進(jìn)程環(huán)境詳解
- 01-10c++中inline的用法分析
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解
- 01-10深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對(duì)數(shù)函數(shù)的表達(dá)式 c語言中對(duì)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 04-02jquery與jsp,用jquery