<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專(zhuān)欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評(píng)論 - 101, 引用 - 0
    數(shù)據(jù)加載中……

    領(lǐng)悟設(shè)計(jì)模式--Template Method / Visitor

    [譯者按] 本文根據(jù)發(fā)表在CUJ Expert Forum上的兩篇文章編譯而成。C/C++ User's Journal是目前最出色的C/C++語(yǔ)言專(zhuān)業(yè)雜志,特別是在C++ Report閉刊之後,CUJ的地位更加突出。CUJ Expert Forum是CUJ主辦的網(wǎng)上技術(shù)專(zhuān)欄,匯集2000年10月以來(lái)C++社群中頂尖專(zhuān)家的技術(shù)短文,并免費(fèi)公開(kāi)發(fā)布,精彩紛呈,是每一個(gè)C/C++學(xué)習(xí)者不可錯(cuò)過(guò)的資料。由Jim Hyslop和Herb Sutter主持的Conversation系列,是CUJ Expert Forum每期必備的精品專(zhuān)欄,以風(fēng)趣幽默的對(duì)話形式講解C++高級(jí)技術(shù),在C++社群內(nèi)得到廣泛贊譽(yù)。譯者特別挑選兩篇設(shè)計(jì)模式方面的文章,介紹給大家。設(shè)計(jì)模式方面的經(jīng)典著作是GoF的Design Patterns。但是那本書(shū)有一個(gè)缺點(diǎn),不好懂。從風(fēng)格上講,該書(shū)與其說(shuō)是為學(xué)習(xí)者而寫(xiě)作的教程范本,還不如說(shuō)是給學(xué)術(shù)界人士看的學(xué)術(shù)報(bào)告,嚴(yán)謹(jǐn)有馀,生動(dòng)不足。這一點(diǎn)包括該書(shū)作者和象Bjarne Stroustrup這樣的大師都從不諱言。實(shí)際上Design Pattern并非一定是晦澀難懂的,通過(guò)生動(dòng)的例子,一個(gè)中等水平的C++學(xué)習(xí)者完全可以掌握基本用法,在自己的編程實(shí)踐中使用,得到立竿見(jiàn)影的功效。這兩篇文章就是很好的例證。本文翻譯在保證技術(shù)完整性的前提下作了不少刪節(jié)和修改,以便使文章顯得更緊湊。

    ----------------------------------------------------------

    人物介紹:

    我 --- 一個(gè)追求上進(jìn)的C++程序員,尚在試用期,聰明但是經(jīng)驗(yàn)不足。

    Wendy --- 公司里的技術(shù)大拿,就坐在我旁邊的隔間里,C++大蝦,最了不起的是,她是個(gè)女的 她什麼都好,就是有點(diǎn)刻薄,

              我對(duì)她真是又崇拜又嫉妒。

    ----------------------------------------------------------

    I. Virtually Yours?-- Template Method模式

    我在研究Wendy寫(xiě)的一個(gè)類(lèi)。那是她為這個(gè)項(xiàng)目寫(xiě)的一個(gè)抽象基類(lèi),而我的工作就是從中派生出一個(gè)具象類(lèi)(concrete class)。這個(gè)類(lèi)的public部份是這樣的:

    class Mountie {
    public:
        void read( std::istream & );
        void write( std::ostream & ) const;
        virtual ~Mountie();

    很正常,virtual destructor表明這個(gè)類(lèi)打算被繼承。那麼再看看其protected部份:

    protected:
        virtual void do_read( std::istream & );
        virtual void do_write( std::ostream & ) const;
    

    也不過(guò)就是一會(huì)兒的功夫,我識(shí)破了Wendy的把戲:她在使用template method模式。public成員函數(shù)read和write是非虛擬的,它們肯定是調(diào)用protected部份do_read/do_write虛擬成員函數(shù)來(lái)完成實(shí)際的工作。啊,我簡(jiǎn)直為自己的進(jìn)步而飄飄然了 哈,Wendy,這回你可難不住我,還有什麼招數(shù)?盡管放馬過(guò)來(lái)... 突然,笑容在我臉上凝固,因?yàn)槲铱吹搅似鋚rivate部份:

    private:
        virtual std::string classID() const = 0;
    

    這是什麼?一個(gè)private純函數(shù),能工作麼?我站了起來(lái),

    “Wendy,你的Mountie類(lèi)好像不能工作耶,它有一個(gè)private virtual function。”

    “你試過(guò)了?”她連頭都不抬。

    “嗯,那倒是沒(méi)有啦,可是想想也不行???我的派生類(lèi)怎麼能override你的private函數(shù)呢?” 我嘟囔 。

    “ ,你倒是很確定啊 ”Wendy的聲音很輕柔,“你怎麼老是這也不行,那也不行的,這幾個(gè)月跟 我你就沒(méi)學(xué)到什麼東西嗎?小菜鳥(niǎo)。”

    真是可惡啊...

    “小菜鳥(niǎo),你全都忘了,訪問(wèn)控制級(jí)別跟一個(gè)函數(shù)是不是虛擬的根本沒(méi)關(guān)系。判斷一個(gè)函數(shù)是動(dòng)態(tài)綁定還是靜態(tài)綁定是函數(shù)調(diào)用解析的最後一個(gè)步驟。好好讀讀標(biāo)準(zhǔn)的3.4和5.2.2節(jié)吧。”

    我完全處?kù)断嘛L(fēng),只好采取干擾戰(zhàn)術(shù)?!昂冒?,就算你說(shuō)的不錯(cuò),我也還是不明白,何必把它設(shè)為private?”

    “我且問(wèn)你,倘若你不想讓一個(gè)類(lèi)中的成員函數(shù)被其他的類(lèi)調(diào)用,應(yīng)當(dāng)如何處理?”

    “當(dāng)然是把它設(shè)置為private,” 我回答道。

    “那麼你去看看我的Mountie類(lèi)實(shí)現(xiàn),特別是write()函數(shù)的實(shí)現(xiàn)?!?/P>

    我正巴不得逃開(kāi)Wendy那刺人的目光,便轉(zhuǎn)過(guò)頭去在我的屏幕上搜索,很快,我找到了:

    void Mountie::write(std::ostream &Dudley) const
    {
        Dudley << classID() << std::endl;
        do_write(Dudley);
    }

    嗨,最近卡通片真是看得太多了,居然犯這樣的低級(jí)失誤。還是老是承認(rèn)吧:“好了,我明白了。classID()是一個(gè)實(shí)現(xiàn)細(xì)節(jié),用來(lái)在保存對(duì)象時(shí)指示具象類(lèi)的類(lèi)型,派生類(lèi)必須覆蓋它,所以必須是純虛的。但是既然是實(shí)現(xiàn)細(xì)節(jié),就應(yīng)該設(shè)為private的?!?/P>

    “這還差不多,小菜鳥(niǎo)?!贝笪r點(diǎn)了點(diǎn)頭,“現(xiàn)在給我解釋一下為什麼do_read()和do_write()是protected的?”

    這個(gè)問(wèn)題并不難,我組織了一下就回答:“因?yàn)榕缮?lèi)對(duì)象需要調(diào)用這兩個(gè)函數(shù)的實(shí)現(xiàn)來(lái)讀寫(xiě)其中的基類(lèi)對(duì)象?!?/P>

    “很好很好,”大蝦差不多滿意了,“不過(guò),你再解釋解釋為什麼我不把它們?cè)O(shè)為public的?”

    現(xiàn)在我感覺(jué)好多了:“因?yàn)檎{(diào)用它們的時(shí)候必須以一種特定的方式進(jìn)行。比如do_write()函數(shù),必須先把類(lèi)型信息寫(xiě)入,再把對(duì)象信息寫(xiě)入,這樣讀取的時(shí)候,負(fù)責(zé)生成對(duì)象的模塊首先能夠知道要讀出來(lái)的對(duì)象是什麼類(lèi)型的,然後才能正確地從流中讀取對(duì)象信息。”

    “聰明啊,我的小菜鳥(niǎo) ”Wendy停頓了一下,“就跟學(xué)習(xí)外國(guó)口語(yǔ)一樣,學(xué)習(xí)C++也不光是掌握語(yǔ)法而已,還必須要掌握大量的慣用法。”

    “是啊是啊,我正打算讀Coplien的書(shū)...”

    [譯者注:就是James Coplien 1992年的經(jīng)典著作Advanced C++ Programming Style and Idioms]

    大蝦揮了揮她的手,“冷靜,小菜鳥(niǎo),我不是指先知Coplien的那本書(shū),我是指某種結(jié)構(gòu)背後隱含的慣用法。比如一個(gè)類(lèi)有virtual destructor,相當(dāng)于告訴你說(shuō):‘嗨,我是一個(gè)多態(tài)基類(lèi),來(lái)繼承我吧 ' 而如果一個(gè)類(lèi)的destructor不是虛擬的,則相當(dāng)於是在說(shuō):‘我不能作為多態(tài)基類(lèi),看在老天的份上,別繼承我。'”

    “同樣的,virtual函數(shù)的訪問(wèn)控制級(jí)別也具有隱含的意義。一個(gè)protected virtual function告訴你:‘你寫(xiě)的派生類(lèi)應(yīng)該,哦,可是說(shuō)是必須調(diào)用我的實(shí)現(xiàn)。'而一個(gè)private virtual function是在說(shuō):‘派生類(lèi)可以覆蓋,也可以不覆蓋我,隨你的便。但是你不可以調(diào)用我的實(shí)現(xiàn)。'”

    我點(diǎn)點(diǎn)頭,告訴她我懂了,然後追問(wèn)道:“那麼public virtual function呢?”

    盡可能不要使用public virtual function。”她拿起一支筆寫(xiě)下了以下代碼:

    class HardToExtend 
    {
    public:
    	 virtual void f();
    };
     void HardToExtend::f() 
    { 
    	// Perform a specific action 
    }

    “假設(shè)你發(fā)布了這個(gè)類(lèi)。在寫(xiě)第二版時(shí),需求有所變化,你必須改用Template Method??墒沁@根本不可能,你知道為什麼?”

    “呃,這個(gè)...,不知道?!?/P>

    兩種可能的辦法。其一,將f()的實(shí)現(xiàn)代碼轉(zhuǎn)移到一個(gè)新的函數(shù)中,然後將f()本身設(shè)為non-virtual

    class HardToExtend
    {
    // possibly protected
        virtual void do_f();
    public:
        void f();
    };
    void HardToExtend::f()
    {
        // pre-processing
        do_f();
        // post-processing
    }
    void HardToExtend::do_f()
    {
        // Perform a specific action
    }
    

    然而你原來(lái)寫(xiě)的派生類(lèi)都是企圖override函數(shù)f()而不是do_f(),你必須改變所有的派生類(lèi)實(shí)現(xiàn),只要你錯(cuò)過(guò)了一個(gè)類(lèi),你的類(lèi)層次就會(huì)染上先知Meyers所說(shuō)的‘精神分裂的行徑'。”[譯者注:叁見(jiàn)Scott Meyers,Effective C++, Item 37,絕對(duì)不要重新定義繼承而來(lái)的非虛擬函數(shù)]

    “另一種辦法是將f()移到private區(qū)域,引入一個(gè)新的non-virtual函數(shù):”

    class HardToExtend
    {
    // possibly protected
        virtual void f();
    public:
        void call_f();
    };

    “這會(huì)導(dǎo)致無(wú)數(shù)令人頭痛的問(wèn)題。首先,所有的客戶(hù)都企圖調(diào)用f()而不是call_f(),現(xiàn)在它們的代碼都不能編譯了。更有甚者,大部份派生類(lèi)都把f()放在public區(qū)域中,這樣直接使用派生類(lèi)的用戶(hù)可以訪問(wèn)到你本來(lái)想保護(hù)的細(xì)節(jié)?!?/P>

    “對(duì)待虛函數(shù)要對(duì)待數(shù)據(jù)成員一樣,把它們?cè)O(shè)為private,直到設(shè)計(jì)上要求使用更寬松的訪問(wèn)控制再來(lái)調(diào)整。要知道由private入public易,由public入private難啊

    [譯者注:這篇文章所表達(dá)的思想具有一定的顛覆性,因?yàn)槲覀兲菀自诨?lèi)中設(shè)置public virtual function了,Java中甚至專(zhuān)門(mén)為這種做法建立了interface機(jī)制,現(xiàn)在竟然說(shuō)這不好 一時(shí)間真是接受不了。但是仔細(xì)體會(huì)作者的意思,他并不是一般地反對(duì)public virtual function,只是在template method大背景下給出上述原則。雖然這個(gè)原則在一般的設(shè)計(jì)中也是值得考慮的,但是主要的應(yīng)用領(lǐng)域還是在template method模式中。當(dāng)然,template method是一種非常有用和常用的模式,因此也決定了本文提出的原則具有廣泛的意義。]

    ----------------------------------------------------------------

    II. Visitor模式

    我正在為一個(gè)設(shè)計(jì)問(wèn)題苦惱。試用期快結(jié)束了,我希望自己解決這個(gè)問(wèn)題,來(lái)證明自己的進(jìn)步。每個(gè)人都記得自己的第一份工作吧,也都應(yīng)該知道在這個(gè)時(shí)候把活兒做好是多麼的重要 我親眼看到其他的新雇員沒(méi)有過(guò)完試用期就被炒了魷魚(yú),就是因?yàn)樗麄儾欢萌绾螌?duì)付那個(gè)大蝦...,別誤會(huì),我不是說(shuō)她不好,她是我見(jiàn)過(guò)最棒的程序員,可就是有點(diǎn)刻薄古怪...?,F(xiàn)在我拜她為師,不為別的,就是因?yàn)槲沂窒M?FONT color=#8080ff>達(dá)到她那個(gè)高度。

    我想在一個(gè)類(lèi)層次(class hierarchy)中增加一個(gè)新的虛函數(shù),但是這個(gè)類(lèi)層次是由另外一幫人維護(hù)的,其他人碰都不能碰:

    class Personnel
    {
    public:
      virtual void Pay ( /*...*/ ) = 0;
      virtual void Promote( /*...*/ ) = 0;
      virtual void Accept ( PersonnelV& ) = 0;
      // ... other functions ...
    };
    
    class Officer : public Personnel { /* override virtuals */ };
    class Captain : public Officer { /* override virtuals */ };
    class First : public Officer { /* override virtuals */ };

    我想要一個(gè)函數(shù),如果對(duì)象是船長(zhǎng)(Captain)就這麼做,如果是大副(First Officer)就那麼做。Virtual function正是解決之道,在Personnel或者Officer中聲明它,而在Captain和First覆蓋(override)它。

    糟糕的是,我不能增加這麼一個(gè)虛函數(shù)。我知道可以用RTTI給出一個(gè)解決方案:

    void f( Officer &o )
    {
      if( dynamic_cast<Captain*>(&o) )
        /* do one thing */
      else if( dynamic_cast<First*>(&o) )
        /* do another thing */
    }
    
    int main()
    {
      Captain k;
      First s;
      f( k );
      f( s );
    }

    但是我知道使用RTTI是公司編碼標(biāo)準(zhǔn)所排斥的行為,我對(duì)自己說(shuō):“是的,雖然我以前不喜歡RTTI,但是這回我得改變對(duì)它的看法了。很顯然,除了使用RTTI,別無(wú)它法?!?/P>

    任何問(wèn)題都可以通過(guò)增加間接層次的方法解決?!?/STRONG>

    我噌地一下跳起來(lái),那是大蝦的聲音,她不知道什麼時(shí)候跑到我背後,“啊喲,您嚇了我一跳...您剛才說(shuō)什麼?”

    “任何問(wèn)...”

    “是的,我聽(tīng)清楚了,”我也不知道哪來(lái)的勇氣,居然敢打斷她,“我只是不知道您從哪冒出來(lái)的?!逼鋵?shí)這話只不過(guò)是掩飾我內(nèi)心的慌張。

    “哈,算了吧,小菜鳥(niǎo),”大蝦斜 眼看 我,“你以為我不知道你心里想什麼 ”她把聲音提高了八度,直盯 我,“那些可憐的C語(yǔ)言門(mén)徒才會(huì)使用switch語(yǔ)句處理不同的對(duì)象類(lèi)型。你看:”

    /* A not-atypical C program */
    void f(struct someStruct *s)
    {
      switch(s->type) {
      case APPLE:
        /* do one thing */
        break;
      case ORANGE:
        /* do another thing */
        break;
      /* ... etc. ... */
      }
    }

    “這些人學(xué)習(xí)Stroustrup教主的C++語(yǔ)言時(shí),最重要的事情就是學(xué)習(xí)如何設(shè)計(jì)好的類(lèi)層次。”

    “沒(méi)錯(cuò),”我又一次打斷她,迫不及待地想讓W(xué)endy明白,我還是有兩下子的,“他們應(yīng)該設(shè)計(jì)一個(gè)Fruit基類(lèi),派生出Apple和Orange,用virtual function來(lái)作具體的事情。

    “很好,小菜鳥(niǎo)。C語(yǔ)言門(mén)徒通常老習(xí)慣改不掉。但是,你應(yīng)該知道,通過(guò)使用virtual function,你增加了一個(gè)間接層次?!彼畔鹿P,“你所需要的不就是一個(gè)新的虛函數(shù)嗎?”

    “是的。可是我沒(méi)有權(quán)力這麼干?!?/P>

    “因?yàn)槟銦o(wú)權(quán)修改類(lèi)層次,對(duì)吧 ”

    “您終於了解了情況,我們沒(méi)法動(dòng)它。也不知道這個(gè)該死的類(lèi)層次是哪個(gè)家伙設(shè)計(jì)的...” 我嘀嘀咕咕 。

    “是我設(shè)計(jì)的?!?/P>

    “啊...,真的? 這個(gè),嘿嘿...”,我極為尷尬。

    “這個(gè)類(lèi)層次必須非常穩(wěn)定,因?yàn)橛锌缙脚_(tái)的問(wèn)題。但是它的設(shè)計(jì)允許你增加新的virtual function,而不必?zé)﹦赗TTI。你可以通過(guò)增加一個(gè)間接層次的辦法解決這個(gè)問(wèn)題。請(qǐng)問(wèn),Personnel::Accept是什麼?”

    ”嗯,這個(gè)...”

    “這個(gè)類(lèi)實(shí)現(xiàn)了一個(gè)模式,可惜這個(gè)模式的名字起得不太好,是個(gè)PNP,叫Visitor模式。”

    [譯者注:PNP,Poor-Named Pattern, 沒(méi)起好名字的模式]

    “啊,我剛剛讀過(guò)Visitor模式。但是那不過(guò)是允許若干對(duì)象之間相互迭代訪問(wèn)的模式,不是嗎?”

    她嘆了一口氣,“這是流行的錯(cuò)誤理解。那個(gè)V,我覺(jué)得毋寧說(shuō)是Visitor,還不如說(shuō)是Virtual更好。這個(gè)PNP最重要的用途是允許在不改變類(lèi)層次的前提下,向已經(jīng)存在的類(lèi)層次中增加新的虛函數(shù)。首先來(lái)看看Personnel及其派生類(lèi)的Accept實(shí)現(xiàn)細(xì)節(jié)?!彼闷鸸P寫(xiě)下:

    void Personnel::Accept( PersonnelV& v )
      { v.Visit( *this ); }
    
    void Officer::Accept ( PersonnelV& v )
      { v.Visit( *this ); }
    
    void Captain::Accept ( PersonnelV& v )
      { v.Visit( *this ); }
    
    void First::Accept ( PersonnelV& v )
      { v.Visit( *this ); }

    “Visitor的基類(lèi)如下:”

    class PersonnelV/*isitor*/
    {
    public:
      virtual void Visit( Personnel& ) = 0;
      virtual void Visit( Officer& ) = 0;
      virtual void Visit( Captain& ) = 0;
      virtual void Visit( First& ) = 0;
    };

    “啊,我記起來(lái)了。當(dāng)我要利用Personnel類(lèi)層次的多態(tài)性時(shí),我只要調(diào)用Personnel::Accept(myVisitorObject)。由於Accept是虛函數(shù),我的myVisitorObject.Visit()會(huì)針對(duì)正確的對(duì)象類(lèi)型調(diào)用,根據(jù)重載法則,編譯器會(huì)挑選最貼切的那個(gè)Visit來(lái)調(diào)用。這不相當(dāng)于增加了一個(gè)新的虛擬函數(shù)了嗎?”

    “沒(méi)錯(cuò),小菜鳥(niǎo)。只要類(lèi)層次支持Accept,我們就可以在不改動(dòng)類(lèi)層次的情況下增加新的虛函數(shù)了?!?/P>

    “好了,我現(xiàn)在知道該怎麼辦了”,我寫(xiě)道:

    class DoSomething : public PersonnelV
    {
    public:
      virtual void Visit( Personnel& );
      virtual void Visit( Officer& );
      virtual void Visit( Captain& );
      virtual void Visit( First& );
    };
    
    void DoSomething::Visit( Captain& c )
    {
      if( femaleGuestStarIsPresent )
        c.TurnOnCharm();
      else
        c.StartFight();
    }
    
    void DoSomething::Visit( First& f )
    {
      f.RaiseEyebrowAtCaptainsBehavior();
    }
    void f( Personnel& p )
    {
      p.Accept( DoSomething() ); // 相當(dāng)于 p.DoSomething()
    }
    
    int main()
    {
      Captain k;
      First s;
    
      f( k );
      f( s );
    }

    大蝦滿意地笑了,“也許這個(gè)模式換一個(gè)名字會(huì)更好理解,可惜世事往往不遂人意...”。

    [譯者注:這篇文章我作了一定的刪節(jié),原文中有稍微多一些的論述,而且提供了兩篇技術(shù)文章的link。]

    from: http://jjhou.csdn.net/myan-design-patterns-big5.htm

    posted on 2005-09-09 16:05 weidagang2046 閱讀(318) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): C/C++

    主站蜘蛛池模板: 亚洲日韩在线观看免费视频| 精品国产人成亚洲区| APP在线免费观看视频| 国产精品亚洲а∨天堂2021| 亚洲国产高清美女在线观看| 色噜噜亚洲精品中文字幕| 四虎永久在线精品免费影视| 国产在线jyzzjyzz免费麻豆| 中文无码日韩欧免费视频| 美女视频黄a视频全免费网站色 | 美女扒开屁股让男人桶爽免费| 亚洲免费在线视频观看| 亚洲AV无码成人专区片在线观看| 国产精品xxxx国产喷水亚洲国产精品无码久久一区| 成人一a毛片免费视频| 青青青国产在线观看免费网站| 日韩免费无码一区二区三区| 一区二区三区无码视频免费福利| eeuss影院www天堂免费| 一级成人生活片免费看| 永久免费精品影视网站| 麻豆91免费视频| 曰批全过程免费视频观看免费软件| 亚洲国产成人AV在线播放| 精品亚洲456在线播放| 亚洲AV成人影视在线观看| 亚洲人成激情在线播放| 国产精品亚洲专区在线观看| 亚洲丝袜中文字幕| 亚洲国产成人精品激情| 亚洲AV无码乱码麻豆精品国产| 亚洲乱码无限2021芒果| 亚洲精品伊人久久久久| 亚洲日韩AV一区二区三区中文| 亚洲一卡一卡二新区无人区| 亚洲女女女同性video| 国产成人高清亚洲一区91| 免费毛片毛片网址| 久久高潮一级毛片免费| 免费黄网站在线观看| 2021在线永久免费视频|