訪問者模式是一種分離對象資料結構與行為的方法,通過這種分離,可以為一個已存在的類或類群(即被訪問者)增加新的操作(即訪問者)而無需為它們作任何修改。訪問者模式屬於行為型模式。
為什麼要使用訪問者模式?
如何擴充一個現有的類階層來實現新行為?一般的方法是給類添加新的方法。但是萬一新行為和現有物件模型不相容怎麼辦?還有,類階層設計人員可能無法預知以後開發過程中將會需要哪些功能。以及,如果已有的類階層不允許修改代碼,怎麼能擴充行為呢?
答案是在類階層設計中使用訪問者模式。
訪問者模式涉及的角色:
1)訪問者(Visitor)
訪問者抽象介面,通過visit(Element)方法訪問Element(資料結構),完成對Element的操作行為。
2)具體訪問者(ConcreteVisitor)
訪問者的具體實作類別。
3)元素(Element),也就是被訪問者
通過accept(Visitor)方法接受Visitor的訪問。
4)具體元素(ConcreteElement)
元素的具體實作類別。
5)對象結構(ObjectStructure)
擁有一組元素的組合對象。ObjectStructure本身也可以作為被訪問者。
訪問者模式的結構圖(網上下載的):
當一個應用滿足以下條件時,我們可以使用Visitor設計模式:
1)一個對象結構包含很多類對象,它們有不同的介面,而你想對這些對象實施一些依賴於其具體類的操作。
2)需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作“汙染”這些對象的類。Visitor使得你可以將相關的操作集中起來定義在一個類中。
3) 當該對象結構被很多應用共用時,用Visitor模式讓每個應用僅包含需要用到的操作。
4) 定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的介面,這可能需要很大的代價。如果對象結構類經常改變,那麼可能還是在這些類中定義這些操作較好。
優點:
1)易於添加那些目前尚未考慮到的方法。(這也是使用訪問者的原因:擴充功能)
2)可以使類更加小巧,因為那些很少使用的方法,可以在外部定義。(意味著如果一個方法經常使用,最好定義在類中;當然在第一次定義中沒有考慮到此方法除外)
缺點:
1)訪問者角色不適合具體元素角色經常發生變化的情況。(如:增加新具體元素類,訪問者介面就需要改變了。)
2)訪問者角色要執行與元素角色相關的操作,就必須讓元素角色將自己內部屬性暴露出來,這就破壞了元素角色的封裝性。訪問者和被訪問的對象的耦合性很大。
3)元素與訪問者之間能夠傳遞的資訊有限,這往往也會限制訪問者模式的使用。(因為訪問者不能直接存取元素的私人資料)
樣本:
#include <iostream><br />#include <list><br />#include <string> </p><p>using namespace std;</p><p>class CPerson;<br />class CStudent;<br />class CTeacher;</p><p>class CVisitor;<br />class CPrinter;</p><p>//元素(被訪問者)<br />class CPerson //純虛類<br />{<br />protected:<br />string name; //或者改成數組<br />int gender; </p><p>CPerson()<br />{<br />}</p><p>public:<br />virtual void Accept( CVisitor& ) //= 0;//純虛函數<br />{<br />}<br />public:<br />void SetName(const string& Name)<br />{<br />name = Name;<br />}</p><p>string GetName() const<br />{<br />return name;<br />}</p><p>void SetGender(const int& Gender)<br />{<br />gender = Gender;<br />}</p><p>int GetGender() const<br />{<br />return gender;<br />}</p><p>};</p><p>//訪問者<br />class CVisitor<br />{<br />public:<br />virtual void Visit( CStudent& ) = 0;<br />virtual void Visit( CTeacher& ) = 0;<br />};</p><p>//具體元素<br />class CStudent: public CPerson<br />{<br />private:<br />int grade; //年級<br />public:<br />CStudent(string Name,int Gender,int Grade)<br />{<br />name=Name;<br />gender=Gender;<br />grade=Grade;<br />}<br />public:<br />virtual void Accept(CVisitor& printer) //虛函數<br />{<br />printer.Visit(*this);<br />}</p><p>void SetGrade(int Grade)<br />{<br />grade=Grade;<br />}</p><p>int GetGrade() const<br />{<br />return grade;<br />}<br />}; </p><p>//具體元素<br />class CTeacher:public CPerson<br />{<br />private:<br />int service_time;//工齡<br />public:<br />CTeacher(string Name,int Grade, int ServiceTime)<br />{<br />name = Name;<br />gender = Grade;<br />service_time = ServiceTime;<br />} </p><p>public: </p><p>virtual void Accept(CVisitor& printer)<br />{<br />printer.Visit(*this);<br />}</p><p>void SetServiceTime(int ServiceTime)<br />{<br />service_time = ServiceTime;<br />}</p><p>int GetServiceTime() const<br />{<br />return service_time;<br />}<br />};</p><p>//具體訪問者<br />//這裡,這個類列印具體元素的資訊(就是顯示在螢幕上),可根據需要列印不同的內容。<br />//具體訪問者是一種策略,可根據不同需要建立新的具體訪問者,而無需修改具體元素(即被訪問者)。<br />class CPrinter: public CVisitor<br />{<br />public:<br />void Visit(CStudent& s)<br />{<br />cout <<"student:" <<endl;<br />cout <<"/t Name:" <<s.GetName() <<endl;<br />if(s.GetGender()==0)<br />cout <<"/t Gender:" <<"female" <<endl;<br />else<br />cout <<"/t Gender:" <<"male" <<endl;<br />cout <<"/t Grade:" <<s.GetGrade() <<endl;<br />}</p><p>void Visit(CTeacher& t)<br />{<br />cout <<"Teacher:" <<endl;<br />cout <<"/t Name:" <<t.GetName() <<endl;<br />if(t.GetGender()==0)<br />cout <<"/t Gender:" <<"female" <<endl;<br />else<br />cout <<"/t Gender:" <<"male" <<endl;<br />cout <<"/t Service Time:" <<t.GetServiceTime()<<endl;<br />}<br />};</p><p>//對象結構<br />//這裡,Organization是個組織(如植樹節組成一個團隊,去植樹),有若干老師,若干學生。<br />class Organization<br />{<br />private:<br />typedef list<CPerson*> CMemberList;<br />CMemberList member_list; </p><p>public: </p><p>void Add(CPerson* person) //增加人員。只是樣本,沒有考慮人員重複等情況。<br />{<br />//注意:person指向的空間必須是由new操作符申請的空間</p><p>member_list.push_back(person);<br />}</p><p>//只是樣本,所以刪除成員等操作略去</p><p>void PrintMembers(CPrinter& printer) //輸出成員名單<br />{<br />CMemberList::iterator itr = member_list.begin();<br />for(; itr != member_list.end(); ++itr)<br />{<br />(*itr)->Accept(printer);<br />}<br />}</p><p>~Organization()<br />{<br />//刪除申請的空間;c++比較麻煩<br />CMemberList::iterator itr = member_list.begin();<br />for(; itr != member_list.end(); ++itr)<br />{<br />delete *itr;<br />}<br />}<br />};</p><p>int main()<br />{<br />Organization Planting;//植樹組織<br />Planting.Add( new CTeacher("Johnny",1,10) );<br />Planting.Add( new CStudent("Catherine",0,1) );<br />Planting.Add( new CStudent("peter",1,2) );</p><p>CPrinter printer;//訪問者,也可看成一種策略<br />Planting.PrintMembers(printer);</p><p>return 0;<br />}
後話:
使用了訪問者模式以後,對於原來的類層次增加新的操作,僅僅需要實現一個具體訪問者角色就可以了,而不必修改整個類層次。而且這樣符合“開閉原則”的要求。而且每個具體的訪問者角色都對應於一個相關操作,因此如果一個操作的需求有變,那麼僅僅修改一個具體訪問者角色,而不用改動整個類層次。但是“開閉原則”的遵循總是片面的。如果系統中的類層次發生了變化,會對訪問者模式產生什麼樣的影響呢?你必須修改訪問者角色和每一個具體訪問者角色。
模式設計教材書經常提及的一句話:發現變化並封裝之。是否採用訪問者模式,就要看(或預見)“變化”是什麼了。訪問者模式中,“變化”是主要是具體訪問者,其次是對象結構。但如果(具體)元素也改變,就萬萬不能用訪問者模式,因為“牽一髮動全身”,維護性就太差了。
再嘮叨一下為什麼使用訪問者模式的個人看法:
對象結構使用的了(具體)元素(即被訪問者),而(具體)元素的功能不全,而直接在元素中添加功能不太好(如:不允許修改元素;或者增加不常用的功能會使類太臃腫等等),那麼我們就定一個輔助類(就是訪問者)來完成這個功能。於是,作為預見性,我們為元素(即被訪問者)增加一個accept(Visitor)方法作為介面,以作不時之需。為元素增加新的功能,只需增加新的訪問者類。
參考文獻:
1.《設計模式初學者指南》,徐迎曉等譯,機械工業出版社
2.網上,東抄一下,西抄一下。