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

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

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

    weidagang2046的專欄

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

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

    人物介紹:

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

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

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

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

    I. Virtually Yours?-- Template Method模式

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

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

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

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

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

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

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

    “Wendy,你的Mountie類好像不能工作耶,它有一個(gè)private virtual function?!?/P>

    “你試過了?”她連頭都不抬。

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

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

    真是可惡啊...

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

    我完全處於下風(fēng),只好采取干擾戰(zhàn)術(shù)?!昂冒桑退隳阏f的不錯(cuò),我也還是不明白,何必把它設(shè)為private?”

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

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

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

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

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

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

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

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

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

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

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

    “是啊是啊,我正打算讀Coplien的書...”

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

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

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

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

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

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

    “假設(shè)你發(fā)布了這個(gè)類。在寫第二版時(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
    }
    

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

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

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

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

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

    [譯者注:這篇文章所表達(dá)的思想具有一定的顛覆性,因?yàn)槲覀兲菀自诨愔性O(shè)置public virtual function了,Java中甚至專門為這種做法建立了interface機(jī)制,現(xiàn)在竟然說這不好 一時(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ì)問題苦惱。試用期快結(jié)束了,我希望自己解決這個(gè)問題,來證明自己的進(jìn)步。每個(gè)人都記得自己的第一份工作吧,也都應(yīng)該知道在這個(gè)時(shí)候把活兒做好是多麼的重要 我親眼看到其他的新雇員沒有過完試用期就被炒了魷魚,就是因?yàn)樗麄儾欢萌绾螌?duì)付那個(gè)大蝦...,別誤會(huì),我不是說她不好,她是我見過最棒的程序員,可就是有點(diǎn)刻薄古怪...。現(xiàn)在我拜她為師,不為別的,就是因?yàn)槲沂窒M?FONT color=#8080ff>達(dá)到她那個(gè)高度。

    我想在一個(gè)類層次(class hierarchy)中增加一個(gè)新的虛函數(shù),但是這個(gè)類層次是由另外一幫人維護(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ì)象是船長(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ì)自己說:“是的,雖然我以前不喜歡RTTI,但是這回我得改變對(duì)它的看法了。很顯然,除了使用RTTI,別無它法。”

    任何問題都可以通過增加間接層次的方法解決。”

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

    “任何問...”

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

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

    /* 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++語言時(shí),最重要的事情就是學(xué)習(xí)如何設(shè)計(jì)好的類層次。”

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

    “很好,小菜鳥。C語言門徒通常老習(xí)慣改不掉。但是,你應(yīng)該知道,通過使用virtual function,你增加了一個(gè)間接層次。”她放下筆,“你所需要的不就是一個(gè)新的虛函數(shù)嗎?”

    “是的??墒俏覜]有權(quán)力這麼干?!?/P>

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

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

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

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

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

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

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

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

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

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

    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的基類如下:”

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

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

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

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

    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)  編輯  收藏 所屬分類: C/C++

    主站蜘蛛池模板: 国产精品青草视频免费播放| 亚洲中文字幕AV每天更新| 青青草国产免费久久久91| 久久精品免费一区二区三区| 激情小说亚洲色图| 亚洲一区中文字幕在线观看| 亚洲精品无码精品mV在线观看| 免费国产小视频在线观看| 成全视频免费高清 | 日本一区免费电影| 99久热只有精品视频免费看| 你懂得的在线观看免费视频| 免费无毒a网站在线观看| 亚洲欧美精品午睡沙发| 精品亚洲国产成人| 亚洲人成影院77777| 亚洲精品在线免费观看| 亚洲AV日韩AV永久无码下载| 国内精品99亚洲免费高清| 四虎永久免费影院在线| 国产最新凸凹视频免费| 女人让男人免费桶爽30分钟| 免费观看AV片在线播放| 亚洲免费在线观看视频| 国产人成免费视频网站| 99re免费视频| 国产一卡二卡四卡免费| 中国在线观看免费高清完整版| 国产成人免费午夜在线观看| 最近中文字幕完整免费视频ww| 97久久免费视频| 亚洲三级高清免费| A在线观看免费网站大全| 97性无码区免费| 成人毛片免费视频| 午夜毛片不卡免费观看视频| 热99re久久精品精品免费| 67194成是人免费无码| 国产午夜影视大全免费观看| 四虎国产精品免费视| 亚洲国产综合无码一区二区二三区 |