2009-11-20 68 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