
這個作業延續 作業五, 但是我們並沒有打算新增程式的功能, (當然如果你覺得你前面繳交的作業功能不全的, 還是可以新增), 我們要修改上一個作業中相同功能程式碼重複多次的問題, 我們希望用另外一個 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 |