Visitor模式的中文名稱是訪問者模式,該模式的目的是提供一個(gè)類來操作其它類型中的對(duì)象結(jié)構(gòu)中的元素(也就是專門幫助其它類來實(shí)現(xiàn)原本屬于它的函數(shù))。它使你可以在不改變各元素類的前提下定義作用于這些元素的新操作。是不是不明白這段話的意思?沒關(guān)系,還是通過例子來理解該模式。我們先來簡(jiǎn)述一下例子。 呵呵,好不容易想到這么個(gè)土的掉渣的例子。別見怪,我實(shí)在想不出更好的例子。例子的背景大家應(yīng)該都非常熟悉,在這兒就不扯淡了。簡(jiǎn)要描述一下我們要實(shí)現(xiàn)的功能。大家都知道在大鬧天宮中,有二郎神和孫悟空打斗的情節(jié)。他們兩個(gè)都有七十二變,七十二變?cè)谖覀兊睦永锵喈?dāng)于七十二個(gè)方法。但他們的變化并不相同,如孫悟空變成廟的時(shí)候,尾巴變不掉,會(huì)變成一個(gè)旗桿;而二郎神沒有尾巴。所以這里把他們各自封裝。幫它們各自提供一個(gè)類。妖怪類Sprite和神仙類God看以下的類圖。 好了,有了類圖,開始開發(fā)。此次要實(shí)現(xiàn)的功能主要是幫助這兩個(gè)類來實(shí)現(xiàn)它的七十二個(gè)方法。我們知道程序肯定不是一次寫完的,每填加幾個(gè)函數(shù),我們就想進(jìn)行一下單元測(cè)試,看看自己的代碼有沒有錯(cuò)誤。這時(shí)候,我們就需要重新編譯程序。由于這兩個(gè)類的方法比較多,這樣每填加一個(gè)函數(shù),就可能需要把這整個(gè)類文件都重新編譯一次。這是件耗費(fèi)時(shí)間的事,你不想看到。那么有沒有辦法幫我們解決該問題呢?有的,就是現(xiàn)在提到的Visitor模式。我們可以把Sprite和God的所有操作提煉成一個(gè)一個(gè)單獨(dú)的類,在這些類中完成原本屬于它們的方法。怎么做呢?先來看看類圖。 在類圖中,你可以看到我們提煉了一個(gè)新類Visitor,它有兩個(gè)子類Change1Vistor和Change2Vistor(如果有其它方法的話,我們可以添加新的類)。Visitor就是我們所說的訪問者類了,就是要通過它來幫助我們把所有的方法都提煉到單獨(dú)的類中。而Change1Vistor和Change2Vistor就是我們所要的具體類,用它來幫助我們實(shí)現(xiàn)神仙和妖怪的變化。我們說過了它們可能有七十二種變化,那么我們?cè)偬罴有碌淖兓臅r(shí)候,就不需要去修改Sprite和God類的內(nèi)容了。(當(dāng)然了,如果真填加七十二中變化的話,這代碼也夠受的,估摸也好不到哪兒去,真有這樣的系統(tǒng),你可能需要去尋找其它方法了。) 我們?cè)倏纯次覀兊腟prite和God,它們擁有共同的基類SuperMan,它只有一個(gè)虛擬函數(shù) Accept(Visitor &) ,就是通過它來實(shí)現(xiàn)對(duì)Visitor類的調(diào)用了。Sprite和God類各自實(shí)現(xiàn)該方法。為了體現(xiàn)Visitor的真正意義,我們給Sprite和God各自添加了成員變量,其實(shí)Visitor的目的主要是幫助處理類中的數(shù)據(jù)成員了。在后面我們將講述這個(gè)問題。好了,還是先來看看具體的代碼,所先來看Visitor的代碼: // Visitor基類 class Visitor { public: //抽象出來針對(duì)于Sprite對(duì)象的方法 virtual void VisitorSprite(Sprite *p) = 0; //抽象出來針對(duì)God對(duì)象的方法 virtual void VisitorGod(God *p) = 0; protected: Visitor(){} }; //針對(duì)于SuperMan的第一個(gè)操作 class Change1Vistor : public Visitor { public: void VisitorSprite(Sprite *p) { cout << "這是妖怪 " << p->GetName() << " 的變化1" << endl; }; void VisitorGod(God *p) { cout << "這是神仙 " << p->GetType() <<" 的變化1" << endl; }; }; //針對(duì)于SuperMan的第一個(gè)操作 class Change2Vistor : public Visitor { public: void VisitorSprite(Sprite *p) { cout << "這是妖怪的變化2" << endl; }; void VisitorGod(God *p) { cout << "這是神仙的變化2" << endl; }; }; 這樣,當(dāng)我們有新的操作需要的時(shí)候,我們就可以重新生成一個(gè)類Change3Visitor,Change4Visitor等等,只要它們都繼承于Visitor就可以了。這樣你可以產(chǎn)生新的文件,而無需重新編譯以前的類文件了。 我們?cè)賮砜匆幌耂uperMan的實(shí)現(xiàn): //超人類 class SuperMan { public: virtual ~SuperMan(){} //抽象出來的調(diào)用方法的接口 virtual void Accept(Visitor &) = 0; protected: SuperMan(){} }; //妖怪類 class Sprite : public SuperMan { private: string m_strName; public: Sprite(string strName):m_strName(strName){} string GetName(){return m_strName;} void Accept(Visitor &v); }; //神仙類 class God : public SuperMan { private: string m_strType; public: God(string strType):m_strType(strType){} string GetType(){return m_strType;} void Accept(Visitor &v); }; //Sprite類的Accept實(shí)現(xiàn) void Sprite::Accept(Visitor &v) { v.VisitorSprite(this); } //God類的Accept實(shí)現(xiàn) void God::Accept(Visitor &v) { v.VisitorGod(this); } 此處,我們需要注意的問題就是Accept的具體實(shí)現(xiàn)了。Sprite和God類需要分別調(diào)用針對(duì)于自己的接口函數(shù)。這里我們還應(yīng)該考慮到一個(gè)問題,就是SuperMan的子類應(yīng)該相對(duì)固定,不應(yīng)該太多的變動(dòng)。當(dāng)它增加一個(gè)子類的時(shí)候,Visitor接口就需要變化,而相應(yīng)的Visitor的所有子類也需要進(jìn)行相應(yīng)的變化。那么這樣與該模式的初衷節(jié)省編譯時(shí)間就背道而馳了,可能要花費(fèi)更多的編譯時(shí)間。 再來看看它的使用方法: int main(int argc, char* argv[]) { Sprite sp("孫悟空"); God g("天神,不是地府之神"); //變化1 Change1Vistor c1; sp.Accept(c1); g.Accept(c1); //變化2 Change2Vistor c2; sp.Accept(c2); g.Accept(c2); return 0; } 我們可以看到Sprite和God對(duì)象調(diào)用相應(yīng)函數(shù)的方法都可以通過Accept來實(shí)現(xiàn)。我們這里的實(shí)現(xiàn)比較簡(jiǎn)單,只是生成了一個(gè)Sprite和一個(gè)God類,而實(shí)際應(yīng)用中它可能是一個(gè)列表,數(shù)組或者是一個(gè)組合(Composite),不過原理一致。其它方式大不過就是需要遍歷所有的元素,并調(diào)用該方法。 再簡(jiǎn)單介紹一下《設(shè)計(jì)模式》中對(duì)該模式提供的例子。在編譯器的實(shí)現(xiàn)過程中,會(huì)將所有源程序組合成一個(gè)語(yǔ)法樹。該語(yǔ)法樹中包括變量,賦值語(yǔ)句,判斷語(yǔ)句等內(nèi)容,這些內(nèi)容都是一個(gè)單獨(dú)的類,而該些類有一個(gè)統(tǒng)一的基類。這些類通過Composite模式組織成一個(gè)結(jié)構(gòu),也就是語(yǔ)法樹。 在這樣的語(yǔ)法樹上,可能有這樣一些操作:類型檢查,代碼優(yōu)化等等,可能還有牽涉打印等,也就是操作是在不斷變化的。而樹的內(nèi)容相對(duì)來說是比較固定的。這樣的話,使用Visitor就可以把這些操作獨(dú)立出來。使新的操作不至于影響原有的類。也不會(huì)使單個(gè)的類變的臃腫。 好了,這就是這次所要說的Visitor模式了。我們可以看到Visitor的初衷是為了節(jié)省編譯時(shí)間也產(chǎn)生的。所以也可以這么說:設(shè)計(jì)模式是開發(fā)經(jīng)驗(yàn)的總結(jié),學(xué)習(xí)它的目的也就是能幫入門者更快的達(dá)到真正理解面向?qū)ο蟮乃健? 參考書目: 1, 設(shè)計(jì)模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ)(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機(jī)械工業(yè)出版社 2, Head First Design Patterns(影印版)Freeman等著 東南大學(xué)出版社 3, 道法自然——面向?qū)ο髮?shí)踐指南 王詠武 王詠剛著 電子工業(yè)出版 |
|