2009-11-20 66 views
1

熊與我爲我傾倒以下簡化代碼:(我將描述該問題下面。)重構函數指針,以某種形式的模板

class CMyClass 
{ 
    ... 
private: 
HRESULT ReadAlpha(PROPVARIANT* pPropVariant, SomeLib::Base *b); 
HRESULT ReadBeta(PROPVARIANT* pPropVariant, SomeLib::Base *b); 

typedef HRESULT (CMyClass::*ReadSignature)(PROPVARIANT* pPropVariant, SomeLib::Base *b); 

HRESULT TryFormats(ReadSignature ReadFormat, PROPVARIANT* pPropVariant); 
}; 


inline HRESULT CMyClass::ReadAlpha(PROPVARIANT* pPropVariant, SomeLib::Base *b) 
{ 
if (b) 
{ 
    // got a valid Base. Handle generic stuff here. 
    SetStuff(pPropVariant, b->someInt); 
    return S_OK; 
} 

return (b != NULL) ? 0 : -1; 
} 

inline HRESULT CMyClass::ReadBeta(PROPVARIANT* pPropVariant, SomeLib::Base *b) 
{ 
if (b) 
{ 
    SomeLib::FormatA *fa; 
    SomeLib::FormatB *fb; 

    if (fa = dynamic_cast<SomeLib::FormatA*>(b)) 
    { 
    // specific code for FormatA 
    SetStuff(pPropVariant, fa->getVersion()); 
    return S_OK; 
    } 
    else if (fb = dynamic_cast<SomeLib::FormatB*>(b)) 
    { 
    // specific code for FormatB 
    SetStuff(pPropVariant, fb->valueForB); 
    return S_OK; 
    } 
} 

return (b != NULL) ? 0 : -1; 
} 

inline HRESULT CMyClass::TryFormats(ReadSignature ReadFormat, PROPVARIANT* pPropVariant) 
{ 
HRESULT hr; 
if (FAILED(hr = (this->*ReadFormat)(pPropVariant, _pFile->formatA()))) 
    if (FAILED(hr = (this->*ReadFormat)(pPropVariant, _pFile->formatC()))) 
    hr = (this->*ReadFormat)(pPropVariant, _pFile->formatD()); 

return hr; 
} 

我最終調用的代碼,如:

hr = TryFormats(&CMyClass::ReadAlpha, pPropVar); 

現在...問題是,這是太泛型和約束,特別是現在,我試圖重構此代碼在其他項目中使用。所以,這意味着我想將ReadXxx代碼放在另一個源文件中,並以某種方式濫用模板。 TryFormats保留在類中,因爲不同的類具有不同的格式,它們嘗試讀取。

我目前的做法是註定要失敗的,由於所需要的功能dynamic_cast<Derived*>不是在Base類,因爲我可能需要一個類來讀取多達5名不同的格式,我真的不想拖累我首先不需要格式。 (例如,見上文CMyClass如何不支持SomeLib::FormatB,但ReadBeta()需要支持它,因此迫使編譯器編譯所有相關信息。)總的來說,我有大約10種不同的格式,我'支持'這樣。

我該如何正確重構這段代碼?我不想爲每個後代重寫Base功能,我也不想將派生的特定信息放入只需要Base的函數中。

我已經嘗試了一些東西,但是我設法從我的編譯器中擠出的所有錯誤都是彩虹。我沒有把這裏的人們與我的企圖混淆起來,而是想給出我的(簡化的)原始工作準則,並讓專家就如何做到這一點得出他們自己的結論。實際上,這些函數中大約有50個函數,但它們要麼遵循上述函數的一般結構ReadAlphaReadBeta函數。所以如果有人能告訴我如何做到這一點,我可以沒有問題轉換我的實際代碼。 (我想我需要更改TryFormats()的定義,這也是沒有問題的 - 我只是希望有人能告訴我如何得到上述示例重構正確。)

謝謝,我道歉很長很長的問題。

+0

看到我的更新您的評論。希望有所幫助。 – 2009-11-20 15:13:53

+1

你不會在同一句話中經常看到的詞:過於籠統和約束 – 2009-11-20 16:33:53

+0

@Doug:越多的想法越好! :) @馬丁約克:真的。儘管如此,他們都適用於我。我認爲問題的根源在於後代對於主要興趣點已經非常一致地開始了,但是對於設計來說沒有考慮次要(更具體的)信息來源......使得獲得這樣的信息無論格式是否真實混亂。 – Stigma 2009-11-20 20:03:55

回答

1

好吧,我以前的visitor的做法是一個歷史。 我將發佈您可以玩的小型工作計劃的全部文本。 假設

_pFile->formatA() 
_pFile->formatC() 
_pFile->formatD() 

所有聲明

FormatA* formatA() 
FormatC* formatC() 
FormatD* formatD() 

換句話說,返回類型是在編譯時已知,這種模板化的方法可以爲你工作。它涉及兩個函數指針,也不動態向下轉換

////////////////////////////////////////////////////////////////// 
// this section is for testing 
class Base  
{ 
public: 
    void ExecuteBase() 
    { 
     cout << "Executing Base" << endl; 
    } 
}; 

class FormatA : public Base 
{ 
public: 
    void ExecuteAAlpha() 
    { 
     cout << "Executing A Alpha" << endl; 
    } 

    void ExecuteABeta() 
    { 
     cout << "Executing A Beta" << endl; 
    } 
}; 

class FormatB : public Base 
{ 
public: 
    void ExecuteBAlpha() 
    { 
     cout << "Executing B Alpha" << endl; 
    } 

    void ExecuteBBeta() 
    { 
     cout << "Executing B Beta" << endl; 
    } 
}; 

FormatA* GetFormatA() 
{ 
    static FormatA cl; 
    return &cl; 
} 

FormatB* GetFormatB() 
{ 
    static FormatB cl; 
    return &cl; 
} 
////////////////////////////////////////////////////////////////// 




////////////////////////////////////////////////////////////////// 
// now begins real code 
struct AlphaReader {}; 
struct BetaReader {}; 
template <typename READER_TYPE> struct TypeConverter {}; 


class MyClass 
{ 
public: 
    template <typename READER_TYPE> 
    int TryFormats(const READER_TYPE&) 
    { 
     TryFormatsImplementation(TypeConverter<READER_TYPE>(), GetFormatA()); 
     TryFormatsImplementation(TypeConverter<READER_TYPE>(), GetFormatB()); 

     return 0; 
    } 

private: 
    int  TryFormatsImplementation(const TypeConverter<AlphaReader>&, Base* pFormat) 
    { 
     // here you will call you ReadAlpha which requires Base only 
     // code below is for testing 

     cout << "Executing Alpha Reader for Base" <<endl; 
     pFormat->ExecuteBase(); 
     return 1; 
    } 

    int  TryFormatsImplementation(const TypeConverter<BetaReader>&, FormatA* pFormat) 
    { 
     // here you will call you ReadBeta for FromatA, 
     // code below is for testing 

     cout << "Executing Beta Reader for FormatA" <<endl; 
     pFormat->ExecuteABeta(); 
     return 3; 
    } 

    int  TryFormatsImplementation(const TypeConverter<BetaReader>&, FormatB* pFormat) 
    { 
     // here you will call you ReadBeta for FromatB, 
     // code below is for testing 

     cout << "Executing Beta Reader for FormatB" <<endl; 
     pFormat->ExecuteBBeta(); 
     return 4; 
    } 
}; 


int main() 
{ 
    MyClass cl; 

    cl.TryFormats(AlphaReader()); 
    cl.TryFormats(BetaReader()); 

    cin.get(); 
} 

後,我運行這個程序,我獲得以下輸出這是正確的:

Executing Alpha Reader for Base 
Executing Base 
Executing Alpha Reader for Base 
Executing Base 
Executing Beta Reader for FormatA 
Executing A Beta 
Executing Beta Reader for FormatB 
Executing B Beta 
+0

對於我來說,現在晚上對我來說已經太遲了。然而,快速瀏覽似乎說這正是我正在尋找的。還有一個問題(我可能很容易測試什麼時候休息一下,但在這裏問它不會有什麼傷害)..將實現從課程中拿出來並在那裏留下TryFormats()應該是相當簡單的,正確? 我會在早上看到這些結構如何操作。看來你是以某種方式調用它們'AlphaReader()'?有趣! – Stigma 2009-11-20 22:48:58

+0

是的,沒什麼說的, TryFormatsImplementation 必須是類成員。 這些只是一堆重載函數。關鍵是編譯器能夠選擇在編譯時調用哪個函數(重載分辨率是靜態的)。 – BostonLogan 2009-11-20 23:21:00

+0

好吧,所以我還沒有睡覺,因爲這個問題仍然佔據我的頭腦。我想知道..模板只有在一個類型可以外推後才能應用,那麼它在這裏如何工作?如果我能想出一個測試它的好方法,我可能會測試明天......但是假設我要在你的例子中的TryFormat函數中刪除/註釋關於'FormatB'的行,它會不會編譯/鏈接與FormatB相關的東西?或者它總是包含所有格式?我可以想象後者發生在你的詭計(我還不能完全掌握)。 – Stigma 2009-11-21 00:33:57

0

更新於評論 我會將SomeLib :: Base包裝在適配器的控制下。給它2個[純]虛擬方法,其目的是向SetStuff提供第二個參數,如果給定的方法(?) - 即alpha/beta - 被支持,則返回第二個參數。然後還提供對底層SomeLib :: class的訪問。

class BaseAdapter 
{ 
... 
private: 
    SomeLib::Base* m_concreteBase; 
public: 
    virtual int SecondArgument(...) = 0; 
    virtual bool IsSupported(...) { return false;} 

    SomeLib::Base* GetConcreteBase() {return m_concreteBase;} 
}; 

class FormatAAdapter : public BaseAdapter 
{ 
    ... 
    int SecondArgument(alphaOrBetaOrWhatever) 
    { 
     // return based on source function 
    } 

    bool IsSupported(alphaOrBetaOrWhatever) 
    { 
     // return true/false based on source function 
    } 
} 

// Create a function to create one of each type of format, ensuring type safety 
virtual BaseAdapter* MakeBaseAdapter(SomeLib::FormatA* fa) 
{ 
     return new FormatAAdapter(fa) 
} 

然後,而不是

SomeLib::FormatA *fa; 
    SomeLib::FormatB *fb; 

    if (fa = dynamic_cast<SomeLib::FormatA*>(b)) 
    { 
    // specific code for FormatA 
    SetStuff(pPropVariant, fa->getVersion()); 
    return S_OK; 
    } 
    else if (fb = dynamic_cast<SomeLib::FormatB*>(b)) 
    { 
    // specific code for FormatB 
    SetStuff(pPropVariant, fb->valueForB); 
    return S_OK; 
    } 

你可以做

ReadBeta(PROPVARIANT* pPropVariant, BaseAdapter *b) 
{ 

    // specific code for FormatB 
    if (b->IsSupported(beta)) 
    { 
     SetStuff(pPropVariant, b->SecondArgument(beta)); 
     return S_OK; 
    } 
} 

在調用代碼,你會通過你的適配器工作:

inline HRESULT CMyClass::TryFormats(ReadSignature ReadFormat, PROPVARIANT* pPropVariant) 
{ 
HRESULT hr; 
if (FAILED(hr = (this->*ReadFormat)(pPropVariant, MakeBaseAdapter(_pFile->formatA()))) 
    if (FAILED(hr = (this->*ReadFormat)(pPropVariant, MakeBaseAdapter(_pFile->formatC())))) 
    hr = (this->*ReadFormat)(pPropVariant, MakeBaseAdapter(_pFile->formatD())); 

return hr; 
} 

此外,響應

基地後代的一個很好的協議將 不支持特定 secondArgument,如果他們做到了,它 可能被計算出來。使用的#ifdefs 將是一個清潔的解決方案(但我更喜歡 模板!)

您可以提供的secondArgument缺省值或提供通過底座適配器的方式通知用戶,該secondArgument不可用。

順便說一句,當我聽到「重構函數指針,以某種形式的模板化的」我想boost functions.

+0

問題在於我需要修改SomeLib,這是我想盡可能避免的。爲了進行64位編譯,我已經保留了一些小修補程序,但是我不想爲了簡單的目的進行更深入的研究,以便在出於任何原因新版本發佈時能夠輕鬆升級。 實際上,差異比單個變量稍大。很多基地後代不會支持這個具體的第二個論點,如果他們這樣做了,它可能會被計算出來。 使用#IFDEFs將是一個更清潔的解決方案(但我更喜歡模板!) – Stigma 2009-11-20 15:00:57

0

對不起,我長的帖子。
一種可能的解決方案是實現訪問者模式。不幸的是,它需要一次修改SomeLib,但在此之後,您可以擴展其功能,無需進一步修改。事實上Visitor是一個支持Open/Close原則的框架。實施一次,您將能夠添加功能到您的圖書館,而無需對圖書館本身進行實際修改。

下面是實現素描:
在SomeLib聲明新類:

// visitor base class, acts as interface can not be instanciated. 
// must be delared in SomeLib 
class IVisitor 
{ 
protected: 
IVisitor() {} 

public: 
// for all supported formats 
virtual HRESULT OnVisitFormatA(SomeLib::FormatA& formatA) 
                 {return NoOperation();} 
virtual HRESULT OnVisitFormatB(SomeLib::FormatB& formatB) 
                 {return NoOperation();} 

private: 
HRESULT NoOperation() {return 0;} 
}; 

在你的每個類SomeLib::base層次必須實現新的虛擬功能:

實施 Accept
virtual HRESULT Accept(IVisitor& visitor); 

將是一流的具體:

HRESULT FormatA::Accept(IVisitor& visitor) 
{ 
return visitor.OnVisitFormatA(*this); 
} 

HRESULT FormatB::Accept(IVisitor& visitor) 
{ 
return visitor.OnVisitFormatB(*this); 
} 

現在我們完成對SomeLib的更改 讓我們轉到您的應用程序。
首先,我們需要採取具體的訪問類:

class CMyClass; // forward delare 
class Visitor : public SomeLib::IVisitor 
{ 
protected: 
Visitor(CMyClass* myclass, PROPVARIANT* propvariant) 
     : myclass_(myclass), propvariant_(propvariant) 
{ 
}; 

protected: 
CMyClass* myclass_; 
PROPVARIANT* propvariant_ 
}; 

這仍然是不可instanciable類。
現在我們需要具體的類來閱讀你需要的東西。

class ReadAlphaVisitor : Visitor 
{ 
public: 
ReadAlphaVisitor(CMyClass* myclass, PROPVARIANT* propvariant) 
      : Visitor(myclass, propvariant) 
{ 
} 

public: 
virtual HRESULT OnVisitFormatA(SomeLib::FormatA& formatA) 
                {return ReadAlpha(formatA);} 
virtual HRESULT OnVisitFormatB(SomeLib::FormatB& formatB) 
                {return ReadAlpha(formatB);} 

private: 
HRESULT ReadAlpha(SomeLib::base& format) 
{ 
    myclass_->SetStuff(propvariant_, format.someInt); 
    return S_OK; 
} 
}; 

而另一個問題:

class ReadBetaVisitor : Visitor 
{ 
public: 
ReadBetaVisitor(CMyClass* myclass, PROPVARIANT* propvariant) 
       : Visitor(myclass, propvariant) 
{ 
} 

public: 
virtual HRESULT OnVisitFormatA(SomeLib::FormatA& formatA) 
               {return ReadBetaFormatA(formatA);} 
virtual HRESULT OnVisitFormatB(SomeLib::FormatB& formatB) 
               {return ReadBetaFormatB(formatB);} 

private: 
HRESULT ReadBetaFormatA(SomeLib::FormatA& formatA) 
{ 
    myclass_->SetStuff(propvariant_, formatA.getVersion()); 
    return S_OK; 
} 

HRESULT ReadBetaFormatB(SomeLib::FormatA& formatB) 
{ 
    myclass_->SetStuff(propvariant_, formatB.valueForB); 
    return S_OK; 
} 
}; 

最後這裏是MyClass的將如何使用它們:

inline HRESULT CMyClass::ReadAlpha(PROPVARIANT* pPropVariant, SomeLib::Base *b) 
{ 
if(0 != b) 
{ 
    ReadAlphaVisitor visitor(this, pPropVariant); 
    return b->Accept(visitor); 
} 

return 0; 
} 

inline HRESULT CMyClass::ReadBeta(PROPVARIANT* pPropVariant, SomeLib::Base *b) 
{ 
if(0 != b) 
{ 
    ReadBetaVisitor visitor(this, pPropVariant); 
    return b->Accept(visitor); 
} 

return 0; 
} 

它害怕我只是看它:-)
它可能經過重新設計,但仍然是一個很好的練習。

更新: 以避免包括所有格式IVisitor可以重新定義如下:

class IVisitor 
{ 
protected: 
IVisitor() {} 

public: 
// for all supported formats 
virtual HRESULT OnVisitFormatA(SomeLib::base& formatA) 
                 {return NoOperation();} 
virtual HRESULT OnVisitFormatB(SomeLib::base& formatB) 
                 {return NoOperation();} 

private: 
HRESULT NoOperation() {return 0;} 
}; 

然後應用程序,使用您的lib將實現遊客和必要的東西(OnVisitFormatA只)超越,但隨後的當然涉及下降(argh ...),我們又回到了繪圖板上,這種設計不會避免下傾並進入垃圾桶。

+0

這確實是過度設計的奇蹟。不錯,但! :) 但嚴重..我看到一些'缺陷'相比,我在找什麼。首先(我承認只在其他答案的評論中寫道)是我想避免對SomeLib的更改。其次是(糾正我,如果我錯了),它仍然意味着編譯每一種格式和庫需要支持它。假設一個新的應用程序只需要支持FormatA,編譯器仍然會包含所有其他格式(加上依賴關係)。這就是爲什麼我正在尋找模板(我承認自己無法自己工作)。 – Stigma 2009-11-20 19:56:58