我们请了一位工程师编写了一个程序,该程序只需将图书馆A的所有书籍录入然后打印出所有书籍的信息。同时我们还请了另外一位工程师编写相同的程序,不同的是他需要将图书馆B的书籍录入然后打印出书籍的信息。对于两位工程师,我们给了一个相同的数据结构:
#define DEFAULT_SIZE 1024 typedef struct _info { string _name; string _lib; int price; }BOOKINFO, *PBOOKINFO;
|
Ok,it’s a piece of cake,工程师a的代码如下:
class CLibraryABooks { public: CLibraryABooks(int iSize = DEFAULT_SIZE); ~CLibraryABooks();
public: void AddBook(string& name, string& lib, int price);
const PBOOKINFO GetArray() const;
private: PBOOKINFO m_pArray; int m_iSize; int m_iCurrent; };
// CLibraryABooks CLibraryABooks::CLibraryABooks(int iSize /* = DEFAULT_SIZE */) : m_iSize(iSize), m_iCurrent(0) { if(iSize <= 0){ m_pArray = NULL; } else{ m_pArray = new BOOKINFO[m_iSize]; } }
CLibraryABooks::~CLibraryABooks() { delete[] m_pArray; }
void CLibraryABooks::AddBook(string& name, string& lib, int price) { m_pArray[m_iCurrent]._name = name; m_pArray[m_iCurrent]._lib = lib; m_pArray[m_iCurrent].price = price;
m_iCurrent ++; }
const PBOOKINFO CLibraryABooks::GetArray() const { return m_pArray; }
|
工程师对于STL很熟悉,自己也比较懒,所以适用了STL中的list,代码如下:
class CLibBBooks { public: CLibBBooks(int iSize = DEFAULT_SIZE); ~CLibBBooks();
public: void AddBook(string& name, string& lib, int price);
const list<PBOOKINFO>* GetList() const;
private: list<PBOOKINFO> m_list; };
// CLibBBooks CLibBBooks::CLibBBooks(int iSize /* = DEFAULT_SIZE */) : m_list(iSize) { }
CLibBBooks::~CLibBBooks() { }
void CLibBBooks::AddBook(string& name, string& lib, int price) { PBOOKINFO pInfo = new BOOKINFO;
pInfo->_name = name; pInfo->_lib = lib; pInfo->price = price;
m_list.push_back(pInfo); }
const list<PBOOKINFO>* CLibBBooks::GetList() const { return &m_list; }
|
好了,拿着两位工程师的代码,我们开始了我们的工作,打印出所有书籍的名字
class CLib { public: CLib(); ~CLib();
public: void Display();
private: CLibraryABooks libA; CLibBBooks libB; };
void CLib::Display() { const PBOOKINFO pArray = libA.GetArray(); int iSize = libA.GetSize();
for(int i=0; i<iSize; i++){ PBOOKINFO pInfo = pArray[i]; string szInfo = pInfo->_lib + "" + pInfo->_name; cout << szString << endl; }
const list<PBOOKINFO>* pList = libB.GetList();
for(list<PBOOKINFO>::iterator iter = pList->begin(); iter != pList->end(); iter++){ PBOOKINFO pInfo = (*iter); string szInfo = pInfo->_lib + "" + pInfo->_name; cout << szString << endl; } }
|
Ok,程序可以运行了,但是,这里似乎有些问题。
1、 我们的Display()函数的实现太过依赖前面的两个类的实现,它是面向实现编程而不是接口编程。
2、 如果那天我们的其中一个类的内部存储格式改变了,那么我们的可爱的Display()函数还得修改。
3、 在这里,要想实现Display()函数,那么我们就必须要知道类CLibraryABooks和CLibBBooks的数据的具体存储格式,也就是说,我们违反了OOP编程的基本准则。
那么我们看看应该怎么改过来。我们再来看看Display中不一样的代码:
for(int i=0; i<iSize; i++){ PBOOKINFO pInfo = pArray[i]; }
|
和
for(list<PBOOKINFO>::iterator iter = pList->begin(); iter != pList->end(); iter++){ PBOOKINFO pInfo = (*iter); }
|
还记得设计模式中经常提到的那个原则吗:对变化的概念进行封装。这里变化的就是操作数据的方式。也许我们更加希望不管是那种存储格式,我们在操作的时候都使用以下统一的格式:
Iterator iter = libA.CreateIterator(); while(iter.HasNext()){ PBOOKINFO pInfo = iter.Next(); // .... }
|
恩,这种方式看上去比刚才我们使用的方式简单多了。既然如此,我们就回头去修改那两个存储数据的类,让他们提供一个统一的接口吧。好想法,但是,有那么多代码需要修改,难道就没有简单一点的方法?让我们进入正题吧。
首先我们声明一个统一的接口:
class Iterator { public: virtual BOOL HasNext() = 0; virtual PBOOKINFO Next() = 0; };
|
然后我们继承一个类下来专门处理
class CArrayIterator : public Iterator { public: CArrayIterator(PBOOKINFO pArray, int iSize);
public: BOOL HasNext(); PBOOKINFO Next();
private: PBOOKINFO m_pArray; int m_iSize; int m_iIndex; };
// CArrayIterator CArrayIterator::CArrayIterator(PBOOKINFO pArray, int iSize) : m_pArray(pArray), m_iSize(iSize) { m_iIndex = 0; }
BOOL CArrayIterator::HasNext() { return m_iIndex <= m_iSize; }
PBOOKINFO CArrayIterator::Next() { PBOOKINFO pInfo = m_pArray[m_iIndex]; m_iIndex ++;
return pInfo; }
|
对于List的类,我们同样有处理它的Iterator
class CListIterator : public Iterator { public: CListIterator(list<PBOOKINFO>* pList);
public: bool HasNext(); PBOOKINFO Next();
private: list<PBOOKINFO>* m_pList; list<PBOOKINFO>::iterator _iter; };
// CListIterator CListIterator::CListIterator(list<PBOOKINFO>* pList) : m_pList(pList) { _iter = m_pList->begin(); }
bool CListIterator::HasNext() { return _iter != m_pList->end(); }
PBOOKINFO CListIterator::Next() { PBOOKINFO pInfo = (*_iter); _iter++; return pInfo; }
|
我们需要改写一下CLibraryABooks和CLibBBooks
class CLibraryABooks { public: const Iterator* GetIterator() { return new CArrayIterator(m_pArray, m_iSize); }
};
class CLibBBooks { public:
const Iterator* GetIterator() { return new CListIterator(m_pList); } };
|
在来看看我们的Display函数该怎么编写:
class CLib { public: CLib(); ~CLib();
public: void Display();
private: void Print(Iterator* _iter);
private: CLibraryABooks libA; CLibBBooks libB; };
void Display() { Iterator* _iter = NULL;
_iter = libA.GetIterator(); Print(_iter);
_iter = libB.GetIterator(); Print(_iter); }
void Print(Iterator* _iter) { PBOOKINFO pInfo; string szMsg;
while(_iter->HasNext()){ pInfo = _iter->Next(); szMsg = pInfo->_lib + ":" + pInfo->_name; cout << szMsg << endl; } }
|
呵呵,很清晰,看看UML结构:
恩,结构已经很清晰了,至少在Iterator这边是很清晰了,但是,我们继续让它更清晰一些。
class CContainer { public: virtual const Iterator* GetIterator() = 0; };
class CLibraryABooks : public CContainer { public: const Iterator* GetIterator(); // some other action else };
class CLibBBooks : public CContainer { public: const Iterator* GetIterator(); // some other action else };
|
再来看看这次设计的UML结构:
仔细体会一下我们的Iterator是怎么和CContainer协同工作的。也许你会说,为什么需要这么麻烦?在最开始我们直接在类CLibraryABooks和CLibBBooks里面添加HasNext()和Next()接口不就可以了?也就是说,我们在上面这种UML中的CContainer中添加这两个接口,去掉GetIterator()接口,这样右边的Iterator就直接可以去掉了。没错,那样的话程序的确可以运行,那么它有什么不好呢?记住:一个类最好只有一个功能。即如果类需要修改时,那么引起它修改的原则只可能是一个。这样做的好处是:可以减少类将来改变的机会,降低出错的几率。
好了,说了这么多,来看看Iterator模式正式的UML框架结构吧。
图中,Aggregate提供了一个公共的接口GetIterator(),然后通过Iterator进行实际的数据遍历或者其他操作,这样就把存储数据的具体实现方法和Client隔离开来,实现了松耦合。注意Iterator模式里,对存储的数据操作时,并不关心数据是否已排序,它只是从头到尾遍历而已。