在转储以下简化代码时请耐心等待:(我将在下面描述问题。)
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个,但它们要么遵循上面ReadAlpha
或ReadBeta
函数的一般结构。因此,如果有人可以告诉我如何做到这些,我可以毫无问题地转换我的实际代码。 (我想我也需要更改TryFormats()
定义,这也没问题 - 我只是希望有人能告诉我怎么做才能正确地重构上面的例子。)
谢谢你,我为这个冗长的问题道歉。
答案 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 ......)并且我们回到绘图板,这设计不会避免向下倾斜并进入垃圾箱。