<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法 作者:Scott Meyers 譯者:fatalerror99 (iTePub's Nirvana) 發布:http://blog.csdn.net/fatalerror99/ 現在你工作在一個視頻游戲上,你在游戲中為角色設計了一個 hierarchy(繼承體系)。你的游戲中有著變化多端的惡劣環境,角色被傷害或者其它的健康狀態降低的情況并不罕見。因此你決定提供一個 member function(成員函數)healthValue,它返回一個象征角色健康狀況如何的整數。因為不同的角色計算健康值的方法可能不同,將 healthValue 聲明為 virtual(虛擬)似乎是顯而易見的設計選擇: ``` class GameCharacter { public: virtual int healthValue() const; // return character's health rating; ... // derived classes may redefine this }; ``` healthValue 沒有被聲明為 pure virtual(純虛)的事實暗示這里有一個計算健康值的缺省算法(參見 Item 34)。 這確實是一個顯而易見的設計選擇,而在某種意義上,這是它的缺點。因為這樣的設計過于顯而易見,你可能不會對它的其它可選方法給予足夠的關注。為了幫助你脫離 object-oriented design(面向對象設計)的習慣性道路,我們來考慮一些處理這個問題的其它方法。 The Template Method Pattern via the Non-Virtual Interface Idiom(經由非虛擬接口慣用法實現的模板方法模式) 我們以一個主張 virtual functions(虛擬函數)應該幾乎總是為 private(私有的)的有趣觀點開始。這一觀點的擁護者提出:一個較好的設計應該保留作為 public member function(公有成員函數)的 healthValue,但應將它改為 non-virtual(非虛擬的)并讓它調用一個 private virtual function(私有虛擬函數)來做真正的工作,也就是說,doHealthValue: ``` class GameCharacter { public: int healthValue() const // derived classes do not redefine { // this - see Item 36 ... // do "before" stuff - see below int retVal = doHealthValue(); // do the real work ... // do "after" stuff - see below return retVal; } ... private: virtual int doHealthValue() const // derived classes may redefine this { ... // default algorithm for calculating } // character's health }; ``` 在這個代碼(以及本 Item 的其它代碼)中,我在類定義中展示 member functions(成員函數)的本體。就像 Item 30 中所解釋的,這會將它們隱式聲明為 inline(內聯)。我用這種方法展示代碼僅僅是這樣更易于看到它在做些什么。我所描述的設計與是否 inline 化無關,所以不必深究 member functions(成員函數)定義在類的內部有什么意味深長的含義。根本沒有。 這個基本的設計——讓客戶通過 public non-virtual member functions(公有非虛擬成員函數)調用 private virtual functions(私有虛擬函數)——被稱為 non-virtual interface (NVI) idiom(非虛擬接口慣用法)。這是一個更通用的被稱為 Template Method(一個模式,很不幸,與 C++ templates(模板)無關)的 design pattern(設計模式)的特殊形式。我將那個 non-virtual function(非虛擬函數)(例如,healthValue)稱為 virtual function's wrapper(虛擬函數的外殼)。 NVI idiom(慣用法)的一個優勢通過 "do 'before' stuff" 和 "do 'after' stuff" 兩個注釋在代碼中標示出來。這些注釋標出的代碼片斷在做真正的工作的 virtual function(虛擬函數)之前或之后調用。這就意味著那個 wrapper(外殼)可以確保在 virtual function(虛擬函數)被調用前,特定的背景環境被設置,而在調用結束之后,這些背景環境被清理。例如,"before" stuff 可以包括鎖閉一個 mutex(互斥體),生成一條日志條目,校驗類變量和函數的 preconditions(前提條件)是否被滿足,等等。"after" stuff 可以包括解鎖一個 mutex(互斥體),校驗函數的 postconditions(結束條件),類不變量的恢復,等等。如果你讓客戶直接調用 virtual functions(虛擬函數),確實沒有好的方法能夠做到這些。 涉及 derived classes(派生類)重定義 private virtual functions(私有虛擬函數)(這些重定義函數它們不能調用!)的 NVI idiom 可能會攪亂你的頭腦。這里沒有設計上的矛盾。重定義一個 virtual function(虛擬函數)指定如何做某些事。調用一個 virtual function(虛擬函數)指定什么時候去做。互相之間沒有關系。NVI idiom 允許 derived classes(派生類)重定義一個 virtual function(虛擬函數),這樣就給了它們控制功能如何實現的能力,但是 base class(基類)保留了決定函數何時被調用的權利。乍一看很奇怪,但是 C++ 規定 derived classes(派生類)可以重定義 private inherited virtual functions(私有的通過繼承得到的函數)是非常明智的。 在 NVI idiom 之下,virtual functions(虛擬函數)成為 private(私有的)并不是絕對必需的。在一些 class hierarchies(類繼承體系)中,一個 virtual function(虛擬函數)的 derived class(派生類)實現被期望調用其 base class(基類)的對應物(例如,第 120 頁的例子),而為了這樣的調用能夠合法,虛擬必須成為 protected(保護的),而非 private(私有的)。有時一個 virtual function(虛擬函數)甚至必須是 public(公有的)(例如,polymorphic base classes(多態基類)中的 destructors(析構函數)——參見 Item 7),但這樣一來 NVI idiom 就不能被真正應用。 The Strategy Pattern via Function Pointers(經由函數指針實現的策略模式) NVI idiom 是 public virtual functions(公有虛擬函數)的有趣的可選替代物,但從設計的觀點來看,它比裝點門也多不了多少東西。畢竟,我們還是在用 virtual functions(虛擬函數)來計算每一個角色的健康值。一個更引人注目的設計主張認為計算一個角色的健康值不依賴于角色的類型——這樣的計算根本不需要成為角色的一部分。例如,我們可能需要為每一個角色的 constructor(構造函數)傳遞一個指向健康值計算函數的指針,而我們可以調用這個函數進行實際的計算: ``` class GameCharacter; // forward declaration // function for the default health calculation algorithm int defaultHealthCalc(const GameCharacter& gc); class GameCharacter { public: typedef int (*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {} int healthValue() const { return healthFunc(*this); } ... private: HealthCalcFunc healthFunc; }; ``` 這個方法是另一個通用 design pattern(設計模式)—— Strategy 的簡單應用,相對于基于 GameCharacter hierarchy(繼承體系)中的 virtual functions(虛擬函數)的方法,它提供了某些更引人注目的機動性: * 相同角色類型的不同實例可以有不同的健康值計算函數。例如: ``` class EvilBadGuy: public GameCharacter { public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) { ... } ... }; int loseHealthQuickly(const GameCharacter&); // health calculation int loseHealthSlowly(const GameCharacter&); // funcs with different // behavior EvilBadGuy ebg1(loseHealthQuickly); // same-type charac- EvilBadGuy ebg2(loseHealthSlowly); // ters with different // health-related // behavior ``` * 對于一個指定的角色健康值的計算函數可以在運行時改變。例如,GameCharacter 可以提供一個 member function(成員函數)setHealthCalculator,它被允許代替當前的健康值計算函數。 在另一方面,健康值計算函數不再是 GameCharacter hierarchy(繼承體系)的一個 member function(成員函數)的事實,意味著它不再擁有訪問它所計算的那個對象內部構件的特權。例如,defaultHealthCalc 不能訪問 EvilBadGuy 的 non-public(非公有)構件。如果一個角色的健康值計算能夠完全基于通過角色的 public interface(公有接口)可以得到的信息,這就沒什么問題,但是,如果準確的健康值計算需要 non-public(非公有)信息,就會有問題。實際上,在任何一個你要用 class(類)外部的等價機能(例如,經由一個 non-member non-friend function(非成員非友元函數)或經由另一個 class(類)的 non-friend member function(非友元成員函數))代替 class(類)內部的機能(例如,經由一個 member function(成員函數))的時候,它都是一個潛在的問題。這個問題將持續影響本 Item 的剩余部分,因為所有我們要考慮的其它設計選擇都包括 GameCharacter hierarchy(繼承體系)的外部函數的使用。 作為一個通用規則,解決對“non-member functions(非成員函數)對類的 non-public(非公有)構件的訪問的需要”的唯一方法就是削弱類的 encapsulation(封裝性)。例如,class(類)可以將 non-member functions(非成員函數)聲明為 friends(友元),或者,它可以提供對“在其它情況下它更希望保持隱藏的本身的實現部分”的 public accessor functions(公有訪問者函數)。使用一個 function pointer(函數指針)代替一個 virtual function(虛擬函數)的優勢(例如,具有逐對象健康值計算函數的能力和在運行時改變這樣的函數的能力)是否能抵消可能的降低 GameCharacter 的 encapsulation(封裝性)的需要是你必須在設計時就做出決定的重要部分。 The Strategy Pattern via tr1::function(經由 tr1::function 實現的策略模式) 一旦你習慣了 templates(模板)和 implicit interfaces(隱式接口)(參見 Item 41)的應用,function-pointer-based(基于函數指針)的方法看上去就有些死板了。健康值的計算為什么必須是一個 function(函數),而不能是某種簡單的行為類似 function(函數)的東西(例如,一個 function object(函數對象))?如果它必須是一個 function(函數),為什么不能是一個 member function(成員函數)?為什么它必須返回一個 int,而不是某種能夠轉型為 int 的類型? 如果我們用一個 tr1::function 類型的對象代替一個 function pointer(函數指針)(諸如 healthFunc),這些約束就會消失。就像 Item 54 中的解釋,這樣的對象可以持有 any callable entity(任何可調用實體)(例如,function pointer(函數指針),function object(函數對象),或 member function pointer(成員函數指針)),這些實體的標志性特征就是兼容于它所期待的東西。我們馬上就會看到這樣的設計,這次使用了 tr1::function: ``` class GameCharacter; // as before int defaultHealthCalc(const GameCharacter& gc); // as before class GameCharacter { public: // HealthCalcFunc is any callable entity that can be called with // anything compatible with a GameCharacter and that returns anything // compatible with an int; see below for details typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {} int healthValue() const { return healthFunc(*this); } ... private: HealthCalcFunc healthFunc; }; ``` 就像你看到的,HealthCalcFunc 是一個 tr1::function instantiation(實例化)的 typedef。這意味著它的行為類似一個普通的 function pointer(函數指針)類型。我們近距離看看 HealthCalcFunc 究竟是一個什么東西的 typedef: ``` std::tr1::function<int (const GameCharacter&)> ``` 這里我突出了這個 tr1::function instantiation(實例化)的“target signature(目標識別特征)”。這個 target signature(目標識別特征)是“取得一個引向 const GameCharacter 的 reference(引用),并返回一個 int 的函數”。這個 tr1::function 類型的(例如,HealthCalcFunc 類型的)對象可以持有兼容于這個 target signature(目標識別特征)的 any callable entity(任何可調用實體)。兼容意味著這個實體的參數能夠隱式地轉型為一個 const GameCharacter&,而它的返回類型能夠隱式地轉型為一個 int。 與我們看到的最近一個設計(在那里 GameCharacter 持有一個指向一個函數的指針)相比,這個設計幾乎相同。僅有的區別是目前的 GameCharacter 持有一個 tr1::function 對象——指向一個函數的 generalized(泛型化)指針。除了達到“clients(客戶)在指定健康值計算函數時有更大的靈活性”的效果之外,這個變化是如此之小,以至于我寧愿對它視而不見: ``` short calcHealth(const GameCharacter&); // health calculation // function; note // non-int return type struct HealthCalculator { // class for health int operator()(const GameCharacter&) const // calculation function { ... } // objects }; class GameLevel { public: float health(const GameCharacter&) const; // health calculation ... // mem function; note }; // non-int return type class EvilBadGuy: public GameCharacter { // as before ... }; class EyeCandyCharacter: public GameCharacter { // another character ... // type; assume same }; // constructor as // EvilBadGuy EvilBadGuy ebg1(calcHealth); // character using a // health calculation // function EyeCandyCharacter ecc1(HealthCalculator()); // character using a // health calculation // function object GameLevel currentLevel; ... EvilBadGuy ebg2( // character using a std::tr1::bind(&GameLevel::health, // health calculation currentLevel, // member function; _1) // see below for details ); ``` 就個人感覺而言:我發現 tr1::function 能讓你做的事情是如此讓人驚喜,它令我渾身興奮異常。如果你沒有感到興奮,那可能是因為你正目不轉睛地盯著 ebg2 的定義并對 tr1::bind 的調用會發生什么迷惑不解。請耐心地聽我解釋。 比方說我們要計算 ebg2 的健康等級,應該使用 GameLevel class(類)中的 health member function(成員函數)。現在,GameLevel::health 是一個被聲明為取得一個參數(一個引向 GameCharacter 的引用)的函數,但是它實際上取得了兩個參數,因為它同時得到一個隱式的 GameLevel 參數——指向 this。然而,GameCharacters 的健康值計算函數只取得單一的參數:將被計算健康值的 GameCharacter。如果我們要使用 GameLevel::health 計算 ebg2 的健康值,我們必須以某種方式“改造”它,以使它適應只取得唯一的參數(一個 GameCharacter),而不是兩個(一個 GameCharacter 和一個 GameLevel)。在本例中,我們總是要使用 currentLevel 作為 GameLevel 對象來計算 ebg2 的健康值,所以每次調用 GameLevel::health 計算 ebg2 的健康值時,我們就要 "bind"(凝固)currentLevel 來作為 GameLevel 的對象來使用。這就是 tr1::bind 的調用所做的事情:它指定 ebg2 的健康值計算函數應該總是使用 currentLevel 作為 GameLevel 對象。 我們跳過一大堆的細節,諸如為什么 "_1" 意味著“當為了 ebg2 調用 GameLevel::health 時使用 currentLevel 作為 GameLevel 對象”。這樣的細節并沒有什么啟發性,而且它們將轉移我所關注的基本點:在計算一個角色的健康值時,通過使用 tr1::function 代替一個 function pointer(函數指針),我們將允許客戶使用 any compatible callable entity(任何兼容的可調用實體)。很酷是不是? The "Classic" Strategy Pattern(“經典的”策略模式) 如果你比 C++ 更加深入地進入 design patterns(設計模式),一個 Strategy 的更加習以為常的做法是將 health-calculation function(健康值計算函數)做成一個獨立的 health-calculation hierarchy(健康值計算繼承體系)的 virtual member function(虛擬成員函數)。做成的 hierarchy(繼承體系)設計看起來就像這樣: ![](https://box.kancloud.cn/2015-12-29_56820e49a99ce.gif) 如果你不熟悉 UML 記法,這不過是在表示當把 EvilBadGuy 和 EyeCandyCharacter 作為 derived classes(派生類)時,GameCharacter 是這個 inheritance hierarchy(繼承體系)的根;HealthCalcFunc 是另一個帶有 derived classes(派生類)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(繼承體系)的根;而每一個 GameCharacter 類型的對象包含一個指向“從 HealthCalcFunc 派生的對象”的指針。 這就是相應的框架代碼: ``` class GameCharacter; // forward declaration class HealthCalcFunc { public: ... virtual int calc(const GameCharacter& gc) const { ... } ... }; HealthCalcFunc defaultHealthCalc; class GameCharacter { public: explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc) : pHealthCalc(phcf) {} int healthValue() const { return pHealthCalc->calc(*this);} ... private: HealthCalcFunc *pHealthCalc; }; ``` 這個方法的吸引力在于對于熟悉“標準的”Strategy pattern(策略模式)實現的人可以很快地識別出來,再加上它提供了通過在 HealthCalcFunc hierarchy(繼承體系)中增加一個 derived class(派生類)而微調已存在的健康值計算算法的可能性。 Summary(概要) 這個 Item 的基本建議是當你為嘗試解決的問題尋求一個設計時,你應該考慮可選的 virtual functions(虛擬函數)的替代方法。以下是對我們考察過的可選方法的一個簡略的回顧: * 使用 non-virtual interface idiom (NVI idiom)(非虛擬接口慣用法),這是用 public non-virtual member functions(公有非虛擬成員函數)包裝可訪問權限較小的 virtual functions(虛擬函數)的 Template Method design pattern(模板方法模式)的一種形式。 * 用 function pointer data members(函數指針數據成員)代替 virtual functions(虛擬函數),一種 Strategy design pattern(策略模式)的顯而易見的形式。 * 用 tr1::function data members(數據成員)代替 virtual functions(虛擬函數),這樣就允許使用兼容于你所需要的東西的 any callable entity(任何可調用實體)。這也是 Strategy design pattern(策略模式)的一種形式。 * 用 virtual functions in another hierarchy(另外一個繼承體系中的虛擬函數)代替 virtual functions in one hierarchy(單獨一個繼承體系中的虛擬函數)。這是 Strategy design pattern(策略模式)的習以為常的實現。 這不是一個可選的 virtual functions(虛擬函數)的替代設計的詳盡無遺的列表,但是它足以使你確信這些是可選的方法。此外,它們之間互為比較的優劣應該使你考慮它們時更為明確。 為了避免陷入 object-oriented design(面向對象設計)的習慣性道路,時不時地給車輪一些有益的顛簸。有很多其它的道路。值得花一些時間去考慮它們。 Things to Remember 可選的 virtual functions(虛擬函數)的替代方法包括 NVI 慣用法和 Strategy design pattern(策略模式)的各種變化形式。NVI 慣用法本身是 Template Method design pattern(模板方法模式)的一個實例。 將一個機能從一個 member function(成員函數)中移到 class(類)之外的某個函數中的一個危害是 non-member function(非成員函數)沒有訪問類的 non-public members(非公有成員)的途徑。 tr1::function 對象的行為類似 generalized function pointers(泛型化的函數指針)。這樣的對象支持所有兼容于一個給定的目標特征的 callable entities(可調用實體)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看