重构函数指针到某种形式的模板

时间:2009-11-20 14:33:07

标签: c++ templates refactoring function-pointers

在转储以下简化代码时请耐心等待:(我将在下面描述问题。)

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的函数中。

我已经尝试了一些东西,但是我设法挤出我的编译器是错误的彩虹。我没有把这里的人与我的尝试混为一谈,而是认为我会给出(简化的)原始工作代码,并允许专家就如何做到这一点得出自己的结论。实际上,这些ReadXxx函数中约有50个,但它们要么遵循上面ReadAlphaReadBeta函数的一般结构。因此,如果有人可以告诉我如何做到这些,我可以毫无问题地转换我的实际代码。 (我想我也需要更改TryFormats()定义,这也没问题 - 我只是希望有人能告诉我怎么做才能正确地重构上面的例子。)

谢谢你,我为这个冗长的问题道歉。

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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.

答案 2 :(得分: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层次结构中的每个类都必须实现新的虚函数:

virtual HRESULT Accept(IVisitor& visitor);

Accept的实施将是特定于类的:

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_
};

这仍然是非不稳定的阶级 现在我们需要具体的课程,以满足您的需求。

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 ......)并且我们回到绘图板,这设计不会避免向下倾斜并进入垃圾箱。