Iterator Pattern (指位器/迭代器模式)
前言
如果你希望印出一個整數陣列裡所有的內容的話,
你會用下列的程式碼來完成:
int i;
int data[100], current;
i = 0;
while (i<100)
{
current = data[i++];
printf("data[%d]=%d\n", i, current);
}
// for (i=0; i<100; i++)
// printf("data[%d]=%d\n", i, data[i]);
類似這樣子存取整個陣列內容的程式片段使用得非常頻繁,
其中你有一個管理 100 個元素的容器 data,
有一個負責指向目前存取位置的指位器 i,
i=0 是把指位器初始化,
i<100 是在測試目前的指位器是不是合法的,
i++ 是將指位器移到容器中下一個元素上。
在物件導向的程式中除了單純的陣列容器之外,
我們常常使用很多不同的容器物件 (例如:
串列,雙向串列,佇列,堆疊,樹等等),
各種容器物件裡組織和儲存物件的方法都不一樣,
但是一旦希望將容器中所有的物件一一取出時,
我們都可以運用類似上面陣列範例的模型來操作容器物件。
例如:
BookShelf bookShelf;
.....
BookShelfIterator *iter = bookShelf.iterator(); // i = 0
while (iter->hasNext()) // i < 100
{
book = iter->next(); // current = data[i++]
book->printName(cout);
}
// for (iter=bookShelf.iterator(); iter->hasNext();)
// {
// iter->next();
// book->printName(cout);
// }
請注意: hasNext() 這個界面函式的功能比較容易讓人產生混淆,
實際上是指測試一下是否可以執行 next()
這個界面來取得目前的指位器所指到的資料。
範例:Book-BookShelf-BookShelfIterator
class Book
{
public:
Book(const char *name);
~Book();
void printName();
private:
char *m_name;
};
Book::Book(const char *name)
: m_name(new char[strlen(name)+1])
{
strncpy(m_name, name, strlen(name)+1);
}
Book::~Book()
{
delete[] m_name;
}
void Book::printName()
{
cout << m_name << "\n";
}
class BookShelf
{
public:
BookShelf(int maxSize);
~BookShelf();
Book *getBookAt(int index);
void appendBook(Book *book);
int getLength();
BookShelfIterator *iterator();
private:
Book **m_books;
int m_last;
int m_maxSize;
};
BookShelf::BookShelf(int maxSize)
: m_last(0),
m_books(new Book*[maxSize]),
m_maxSize(maxSize)
{
}
BookShelf::~BookShelf()
{
delete[] m_books;
}
Book *BookShelf::getBookAt(int index)
{
if (index < m_maxSize)
return m_books[index];
else
return 0;
}
void BookShelf::appendBook(Book *book)
{
m_book[m_last++] = book;
}
int BookShelf::getLength()
{
return m_last;
}
BookShelfIterator *BookShelf::iterator()
{
return new BookShelfIterator(this);
}
class BookShelfIterator
{
public:
BookShelfIterator(BookShelf *bookShelf);
bool hasNext();
Book *next();
private:
BookShelf *m_bookShelf;
int m_index;
};
BookShelfIterator::BookShelfIterator(BookShelf *bookShelf)
: m_bookShelf(bookShelf),
m_index(0)
{
}
bool BookShelfIterator::hasNext()
{
if (m_index < m_bookShelf->getLength())
return true;
else
return false;
}
Book *BookShelfIterator::next()
{
return m_bookShelf->getBookAt(m_index++);
}
int main()
{
Book *book;
BookShelf *bookShelf = new BookShelf(4);
bookShelf->appendBook(new Book("A Light in the Attic"));
bookShelf->appendBook(new Book("Tom Sawyer"));
bookShelf->appendBook(new Book("Snow White and the Seven Dwarfs"));
bookShelf->appendBook(new Book("Jack and the Beanstalk"));
BookShelfIterator *iter = bookShelf->iterator();
while (iter->hasNext())
{
book = iter->next();
book->printName();
}
return 0;
}
類別圖:
source codes
Iterator Pattern
上面的範例並不是一個一般化的範例,
下面我們利用 Object, Aggreate, 及 Iterator
三個抽象的類別來定義比較一般化的 Iterator pattern。
範例:Object Aggregate-Iterator Book-BookShelf-BookShelfIterator
class Aggregate
{
public:
virtual Iterator *iterator()=0;
};
class Iterator
{
public:
virtual bool hasNext()=0;
virtual Object *next()=0;
};
class Book: public Object
{
public:
Book(const char *name);
~Book();
void printName();
private:
char *m_name;
};
Book::Book(const char *name)
: m_name(new char[strlen(name)+1])
{
strncpy(m_name, name, strlen(name)+1);
}
Book::~Book()
{
delete[] m_name;
}
void Book::printName()
{
cout << m_name << "\n";
}
class BookShelf: public Aggregate
{
public:
BookShelf(int maxSize);
~BookShelf();
Book *getBookAt(int index);
void appendBook(Book *book);
int getLength();
virtual Iterator *iterator();
private:
Book **m_books;
int m_last;
int m_maxSize;
};
BookShelf::BookShelf(int maxSize)
: m_last(0),
m_books(new Book*[maxSize]),
m_maxSize(maxSize)
{
}
BookShelf::~BookShelf()
{
delete[] m_books;
}
Book *BookShelf::getBookAt(int index)
{
if (index < m_maxSize)
return m_books[index];
else
return 0;
}
void BookShelf::appendBook(Book *book)
{
m_books[m_last++] = book;
}
int BookShelf::getLength()
{
return m_last;
}
Iterator *BookShelf::iterator()
{
return (Iterator *)new BookShelfIterator(this);
}
class BookShelfIterator: public Iterator
{
public:
BookShelfIterator(BookShelf *bookShelf);
virtual bool hasNext();
virtual Object *next();
private:
BookShelf *m_bookShelf;
int m_index;
};
BookShelfIterator::BookShelfIterator(BookShelf *bookShelf)
: m_bookShelf(bookShelf),
m_index(0)
{
}
bool BookShelfIterator::hasNext()
{
if (m_index < m_bookShelf->getLength())
return true;
else
return false;
}
Object *BookShelfIterator::next()
{
return (Object *)m_bookShelf->getBookAt(m_index++);
}
int main()
{
Book *book;
BookShelf *bookShelf = new BookShelf(4);
bookShelf->appendBook(new Book("A Light in the Attic"));
bookShelf->appendBook(new Book("Tom Sawyer"));
bookShelf->appendBook(new Book("Snow White and the Seven Dwarfs"));
bookShelf->appendBook(new Book("Jack and the Beanstalk"));
BookShelfIterator *iter = (BookShelfIterator *)bookShelf->iterator();
while (iter->hasNext())
{
book = (Book *)iter->next();
book->printName();
}
return 0;
}
類別圖:
source codes
注意事項:
如果一開始我們不採用類似指位器這樣的模型來設計的話,
我們也可以考慮把 hasNext() 和 next() 這兩個界面函式設計在 BookShelf
類別中,
功能看起來也很合理,
資料的存取也很順暢,
但是有一個功能是沒有辦法達到的,
就是在演算法中沒有辦法同時多次尋訪這個 BookShelf 物件中的書籍,
例如下面的演算法就無法實作:
取下書架上每一本書
請比較該書和所有書架上其它的書籍的作者是否相同
而使用 Iterator Pattern 就可以很輕鬆地實作上面的演算法。
在上面程式碼中 BookShelf 類別提供 getBookAt() 及 getLength()
界面給 BookShelfIterator 類別使用,
在使用 C++ 實作的時候,
有的時候如果 BookShelfIterator 需要存取的私有資料太多的話,
也可以考量使用 friend class 來實作,
在資料存取的便利性和封裝性當中必須做一取捨。
(使用 Java 的話就可以考慮在同一個檔案中來實作這兩個類別)