2013-07-04 42 views
2

我有一個純虛擬方法的類功能。幾個類實現不同API的父類

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual const float getValue(const vector<int>& v) const = 0; 
}; 

該類由幾個類實現,例如FeatureA和FeatureB。 一個單獨的類Computer(simplified)使用getValue方法來進行一些計算。

class Computer { 
public: 
    const float compute(const vector<Feature*>& features, const vector<int>& v) { 
    float res = 0; 
    for (int i = 0; i < features.size(); ++i) { 
     res += features[i]->getValue(v); 
    } 
    return res; 
    } 
}; 

現在,我想實現FeatureC但我意識到我需要在getValue方法的附加信息。在FeatureC的方法看起來像

const float getValue(const vector<int>& v, const vector<int>& additionalInfo) const; 

我當然可以修改的getValue的簽名功能,FeatureA,FeatureB採取additionalInfo作爲參數,並添加additionalInfo作爲計算方法的參數。但是如果我想實現需要更多附加信息的FeatureD,我可能稍後必須再次修改所有這些簽名。我想知道是否有一個更優雅的解決方案,或者如果有一個已知的設計模式,您可以指向我進一步閱讀。

回答

2

您至少有兩種選擇:

  1. 而不是將單個向量傳遞到getValue(),傳遞一個結構。在這個結構中,你可以把今天的矢量,和更多的數據明天。當然,如果程序的某些具體運行不需要額外的字段,則計算它們的需求可能是浪費的。但是,如果您始終需要計算所有數據(即總是存在一個FeatureC),它將不會導致性能損失。
  2. 傳遞給getValue()對具有獲取必要數據的方法的對象的引用。這個對象可以是電腦本身,或者一些更簡單的代理。然後getValue()實現可以精確地請求他們需要什麼,並且它可以被延遲計算。在某些情況下,懶惰會消除浪費的計算,但是這樣做的整體結構會由於必須調用(可能是虛擬的)函數來獲取各種數據而產生一些小的不變的開銷。
+0

謝謝,很棒的選擇! – needsaname

+0

你很受歡迎。我鼓勵你對任何和所有你認爲有用的答案進行投票(並接受你最終找到的最相關的答案)。歡迎來到StackOverflow。 –

1

要求您的要素類層次結構的用戶根據類調用不同的方法會失敗多態性。一旦你開始做dynamic_cast<>()你知道你應該重新考慮你的設計。

如果一個子類只需要它的調用者可以獲得的信息,就應該改變getValue()方法來獲取一個additionalInfo參數,並且簡單地忽略那些無關緊要的類中的信息。

如果FeatureC可以通過調用另一個類或函數來獲取additionalInfo,那通常是一種更好的方法,因爲它限制了需要了解它的類的數量。也許數據可以從FeatureC通過其構造函數或單例對象訪問的對象中獲得,或者可以通過調用函數來計算。尋找最佳方法需要更多關於案件的知識。

+0

「如果子類需要的信息,它只能從它的調用者得到的,你應該改變的getValue()方法來採取additionalInfo參數,並簡單地忽略那些無關緊要的課程中的信息。「:是的,這就是我想要避免的。 「否則,如果FeatureC可以通過調用另一個類或函數來獲得additionalInfo,那麼這是一個更好的方法。」:您能否詳細說明一下?你的意思是說FeatureC在構造函數中獲得additionalInfo例如? – needsaname

0

要正常工作的虛擬功能需要有完全相同的「簽名」(相同的參數和相同的返回類型)。否則,你只會得到一個「新成員函數」,這不是你想要的。

這裏真正的問題是「呼叫代碼如何知道它需要額外的信息」。

你可以用幾種不同的方式解決這個問題 - 第一個是總是通過const vector <int>& additionalInfo,無論是否需要。

如果這是不可能的,因爲沒有,除了在FeatureC的情況下,任何additionalInfo,你可以有一個「可選」的參數 - 這意味着使用指針矢量(vector<int>* additionalInfo),這是空當值不可用。

當然,如果additionalInfo是一個可以存儲在FeatureC類中的值,那麼也可以。

另一種選擇是擴展基類Feature有兩個選項:

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual const float getValue(const vector<int>& v) const = 0; 
    virtual const float getValue(const vector<int>& v, const vector<int>& additionalInfo) { return -1.0; }; 
    virtual bool useAdditionalInfo() { return false; } 
}; 

,然後讓你的循環是這樣的:

for (int i = 0; i < features.size(); ++i) { 
    if (features[i]->useAdditionalInfo()) 
    { 
     res += features[i]->getValue(v, additionalInfo); 
    } 
    else 
    { 
    res += features[i]->getValue(v); 
    } 
} 
1

此問題在C++編碼標準(Sutter,Alexandrescu)的第39項中解決,題爲「考慮使虛擬功能非公共,公共功能非虛擬」。

尤其是動機以下非虛接口設計方案的一個(這是該項目是怎麼一回事)表述爲

每個接口可以利用其自然形狀:當我們從定製界面分離公共界面 ,每個都可以很容易地採取它自然想要採取的形式,而不是試圖找到一個妥協,迫使他們看起來相同 。通常,這兩個接口需要不同數量的功能和/或不同的參數; [...]

這與改變

另一個設計圖案,其是非常有用的在這種情況下的成本高特別有用

在基類是訪問者模式。至於NVI,它適用於基類(以及整個層次結構)具有較高的變更成本。您可以找到關於這種設計模式的大量討論,我建議您閱讀Modern C++(Alexandrescu)中的相關章節,該章節(側面)讓您深入瞭解如何使用(非常容易使用的)Visitor工具在loki

我建議你閱讀所有這些材料,然後編輯問題,以便我們可以給你一個更好的答案。我們可以想出各種解決方案(例如,如果需要,可以使用額外的方法給予課程額外的參數),這可能不適合您的情況。

嘗試解決以下問題:

  1. 將基於模板的解決方案配合的問題?
  2. 在調用函數時添加新的間接層是否可行?
  3. 會有「推參數」 - 「推參數」-...-「推參數」 - 「調用函數」的方法有幫助嗎? (這似乎起初很奇怪,但 認爲像 「COUT < < ARG < < ARG < < ARG < < ENDL」,其中 「ENDL」 是 「調用函數」)
  4. 你怎麼打算區分如何調用Computer :: compute中的函數?

現在我們有一些「理論」,我們的目標是使用Visitor模式的做法:

#include <iostream> 

using namespace std; 

class FeatureA; 
class FeatureB; 

class Computer{ 
    public: 
    int visitA(FeatureA& f); 

    int visitB(FeatureB& f); 
}; 

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual int accept(Computer&) = 0; 
}; 

class FeatureA{ 
    public: 
    int accept(Computer& c){ 
     return c.visitA(*this); 
    } 
    int compute(int a){ 
     return a+1; 
    } 
}; 

class FeatureB{ 
    public: 
    int accept(Computer& c){ 
     return c.visitB(*this); 
    } 
    int compute(int a, int b){ 
     return a+b; 
    } 
}; 

int Computer::visitA(FeatureA& f){ 
     return f.compute(1); 
} 

int Computer::visitB(FeatureB& f){ 
     return f.compute(1, 2); 
} 

int main() 
{ 
    FeatureA a; 
    FeatureB b; 
    Computer c; 
    cout << a.accept(c) << '\t' << b.accept(c) << endl; 
} 

你可以試試這個代碼here。 這是Visitor模式的粗略實現,正如你所看到的,它可以解決你的問題。我強烈建議你不要試圖用這種方式來實現,有明顯的依賴問題可以通過稱爲非循環訪問者的改進來解決。它已經在Loki中實施,所以不必擔心實施它。除了實現之外,正如你所看到的,你不依賴於類型切換(正如其他人指出的那樣,你應該儘可能地避免),並且你不需要類有任何特定的接口(例如一個參數爲計算功能)。此外,如果訪問者類是層次結構(使計算機成爲示例中的基類),則當您要添加此類功能時,不需要爲層次結構添加任何新功能。

如果你不喜歡visitA,visitB,...「模式」,不用擔心:這只是一個簡單的實現,你不需要它。基本上,在真正的實現中,您使用訪問函數的模板特化。

希望這有助於,我已經投入了大量的精力投入到它:)

+0

感謝有趣的指針 – needsaname

+0

歡迎您。我已經爲您的問題制定了更多的材料和代碼解決方案。希望能幫助到你。 –