這個作業延續 作業五, 但是我們並沒有打算新增程式的功能, (當然如果你覺得你前面繳交的作業功能不全的, 還是可以新增), 我們要修改上一個作業中相同功能程式碼重複多次的問題, 我們希望用另外一個 Vistor 樣式來配合 Composite 樣式工作, 以便得到一個比較好的程式架構。雖然這個作業要寫的程式看起來不多, 不過為了不把一個已經測試完成的程式改壞掉, 你還是要花相當的時間,耐心地修改相關的程式, 由於程式的外觀不會有明顯改變, 所以你也特別必須用心體會, 才能夠了解這些修改的真正意義, 以後遇見類似的問題才能夠以比較好的物件導向設計來應付。
做了這個作業以後, 應該會有進一層的體會, 設計程式不只著重在完成一些指定的功能而已, 不見得只是硬湊出一些功能而已, 是可以很有巧思的, 就像是蓋房子, 可以只蓋出四四方方、平平淡淡、僅能遮風避雨的東西, 也可以設計成美觀有創意、有價值、可重複欣賞與應用的真正工藝品。
範例執行程式prog6_v1.exe, 資料檔案prog6a.in, 下載後可以直接透過鍵盤交談式執行,也可以用
prog6_v1 < prog6a.in
直接執行資料檔案中的命令, 程式結束後會將所建立的資料匣及檔案架構存檔, 下次執行會重新讀入。由使用界面和功能上你應該看不到和上一次作業有什麼差別, 這也是這個測試資料檔案的主要目標: 保證具有和上一次作業完全一樣的功能。
例如在計算檔案/資料匣大小的時候, 你需要完成下列這三件事情:
int Folder::getSize() { vector<Entry *>::iterator iter; int sum=0; for (iter=m_entries.begin(); iter<m_entries.end(); iter++) sum += (*iter)->getSize(); return sum; }這段程式假設每一個子資料匣和檔案都會計算它的大小, 然後以多型指標 (*iter) 來委託容器中每一個物件計算它自己的大小。
int File::getSize() { return m_content.size(); }
在 Composite 樣式所描述的異質的樹狀資料架構中, 我們要加入一些功能時, 會依照不同的類別而有不同的作法, 我們把這些功能直接加進去時會使得 Composite 樣式不但要管理異質的樹狀資料架構, 還要同心協力來完成指定的功能, 於是 Composite 樣式中的各個成員變得越來越多也越來越複雜。
用 Visitor 樣式和 Composite 樣式合作時, 最主要就是希望讓 Composite 樣式專心管理異質的樹狀資料架構, 其它的演算法和不斷增加的新功能讓 Visitor 樣式來處理, 增加功能時 Composite 樣式的各個類別根本不需要修改。
用一個比喻來說, Visitor 和 Composite 的合作, 就好像是一個專業的廣告公司 (Visitor) 和它的客戶 (Composite), 如果每一個客戶都要負責自己公司的廣告設計, 用很多的人力卻不一定能把工作做好, 每一個客戶為自己做廣告設計, 也會使得自己公司內人員的業務變得比較複雜, 比較容易出錯, 這時候如果出錯的話其實不是個別人員的問題, 常常是公司管理以及組織架構的問題。 這個時候其實需要一個專業的廣告公司, 在這個廣告公司裡當然比較會是任務導向的, 針對每一種客戶都有一個專門的設計小組 (例如 Visitor 類別中所實作的 visit(File *) 界面就是一個為了處理 File 這個類別的工作小組, visit(Folder *) 界面就是一個為了處理 Folder 這個類別的工作小組), Acceptor 所定義的 accept(Visitor &) 界面最主要的功能就是當專業的廣告公司人員來到客戶公司內部時, 需要由適當的接待人員來將公司的需求向廣告公司的設計人員溝通, 以這個客戶的屬性來挑選廣告公司內專門的設計小組來為其服務。
Visitor 樣式的骨幹如下圖中紅色部份所示:
class Acceptor { public: virtual void accept(Visitor &visitor)=0; };要求 Composite Pattern 中的 Entry 類別繼承它, 也就是要求每一個底層的類別包括 File 和 Folder 都要實作這個界面函式, 不過這個界面函式不論在 File 類別內或是在 Folder 類別內的實作都一樣也都很簡單, 如下所示:
void File::accept(Visitor &visitor) { visitor.visit(this); } void Folder::accept(Visitor &visitor) { visitor.visit(this); }這兩個函式看起來內容完全一樣, 但是其實因為 this 指標所指到物件的型態不一樣, 使它可以完成適當的功能: 它最主要的目的是根據不同物件的類別來決定用 Visitor 類別物件中相對應的 visit() 成員函式來處理實際的功能。
class Visitor { public: virtual void visit(File *file)=0; virtual void visit(Folder *folder)=0; };由於所要拜訪的 Composite 中只有兩種實際的類別, 所以只需要定義兩個不同的純虛擬函式。
class SizeVisitor : public Visitor { public: SizeVisitor():m_totalSize(0) {} void visit(File *file); void visit(Folder *folder); int getTotalSize(); private: int m_totalSize; }; void SizeVisitor::visit(File *file) { m_totalSize += file->getSize(); } void SizeVisitor::visit(Folder *folder) { vector<Entry *>::iterator iter; for (iter=folder->begin(); iter<folder->end(); iter++) (*iter)->accept(*this); } int SizeVisitor::getTotalSize() { return m_totalSize; }其中需要使用 Folder 類別特別定義的 begin() 和 end() 兩個界面,
vector<Entry *>::iterator Folder::begin() { return m_entries.begin(); } vector<Entry *>::iterator Folder::end() { return m_entries.end(); }假設 entry 為你打算計算大小的物件指標, 使用這個 SizeVisitor 的方法如下:
SizeVisitor sv; entry->accept(sv); cout << "The size is " << sv.getSize() << " bytes." << endl;
在物件導向的軟體發展流程中調整軟體的架構我們叫做 refactoring, 這是常常需要做的, 有的時候是為了程式的效率, 有的時候是為了和其它程式發展者建立一致的溝通界面, 有的時候是為了程式的彈性和擴充性: 我們在設計軟體的時候很難設計出一個萬能的架構, 在新的需求出現的時候常常需要更改舊的程式架構來應付。
回
C++ 物件導向程式設計課程
首頁
製作日期: 05/31/2009 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |