我有一个非常复杂的代码结构,但重要的是:
典型的设置:我有一个基类和两个派生自这个基类的类,每个类都有自己的成员,没有标准的构造函数
class BaseSolver{
...
};
class SolverA : BaseSolver{
public:
std::string a;
SolverA(TypeA objectA);
};
class SolverB : BaseSolver{
public:
int b;
SolverB(TypeB objectB);
};
现在我有一个config xml文件,我从中读取是否必须使用SolverA或SolverB.因此我有一个IOService:
template<class T>
class IOService
{
BaseSolver* getSolver()
{
std::string variableThatIReadFromXML;
/* here I have to perform many actions before I can create a solver object
* to retrieve the data needed for the constructors */
TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;
if (variableThatIReadFromXML == "a")
return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
else if (variableThatIReadFromXML == "b")
return new SolverB(anotherVariableIConstrucedWithDataFromXML);
}
};
在我的应用程序的某个地方(为简单起见,我们说它是main.cpp):
int main(){
IOService ioService;
BaseSolver* mySolver = ioService.getSolver();
}
那绝对没问题.
但是现在,主要是我必须分别访问派生类a和b的成员.
我怎样才能做到这一点?
我想从IOService中只修改Solver的类型:
class IOService
{
decltype getSolverType()
{
std::string variableThatIReadFromXML;
/* here I have to perform many actions before I can create a solver object
* to retrieve the data needed for the constructors */
TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;
if (variableThatIReadFromXML == "a")
return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
else if (variableThatIReadFromXML == "b")
return new SolverB(anotherVariableIConstrucedWithDataFromXML);
}
TypeA getConstructorDataForSolverA()
{
/* here I have to perform many actions before I can create a solver object
* to retrieve the data needed for the constructors */
return variableIConstrucedWithDataFromXML;
}
TypeB getConstructorDataForSolverB()
{
/* here I have to perform many actions before I can create a solver object
* to retrieve the data needed for the constructors */
return anotherVariableIConstrucedWithDataFromXML;
}
};
但是我当然不能将decltype指定为返回值.
我真的很无奈.我希望任何暗示正确的方向,甚至是解决这个问题的方法.
[编辑]:派生的解算器类需要的不仅仅是xml文件中的信息才能正常工作.这意味着,我必须设置一些来自网格文件的属性.所以我可以将meshfile提供给IOService,以便IOService可以这样设置适当的成员:
class IOService
{
BaseSolver* getSolver(MeshType myMesh)
{
std::string variableThatIReadFromXML;
/* here I have to perform many actions before I can create a solver object
* to retrieve the data needed for the constructors */
TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;
if (variableThatIReadFromXML == "a")
{
auto solverA = new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
solverA.a = mesh.a;
}
else if (variableThatIReadFromXML == "b")
{
auto solverB = new SolverB(anotherVariableIConstrucedWithDataFromXML);
solverB.b = mesh.b;
}
}
};
但是IOService需要知道MeshType类,我想要避免,因为我认为它打破了封装.
所以我想分别在我的程序的另一部分中设置成员a和b(这里为了简单起见).
考虑到这一点,只有Daniel Daranas的回答对我来说似乎是一个解决方案.但我想避免动态演员.
所以重新提出的问题可能是:我应该如何改变我的设计以确保封装并避免动态演员表? [/编辑]
我正在使用clang 3.4 ob ubuntu 12.04 lts.
解决方法:
关于多态的有趣之处在于,当你不使用它时,它指出了你.
以您的方式继承基类1目的:为具有不同行为的对象公开统一接口.基本上,您希望子类看起来一样.如果我有继承自A的B和C类,我想对该类说“do foo”,它会做foob或fooc.
基本上,你正在翻转它:我有A和B的B和C,如果它是B我想做foob,如果它是C我想做fooc.虽然这看起来很可怕,但通常解决问题的最佳方法是重新解释这个问题.
所以对于你的例子,你现在说“好吧,所以我有一个XML文件,如果我正在制作A,我将以一种方式从中读取数据,或者如果我制作一个B,我会以其他方式读取数据.”但多态方式是“我有一个XML文件.它告诉我制作一个A或一个B,然后我告诉实例解析XML文件”.
因此,解决此问题的方法之一是更改求解器界面:
class BaseSolver
{
public:
virtual void ReadXMLFile(string xml) = 0;
...
};
虽然这会以一种使用多态的方式对问题进行重新定义,并且无需您查看已创建的内容,但您可能不喜欢这样,出于同样的原因我不这样做:您必须提供默认构造函数,它使类处于未知状态.
因此,不是在接口级别强制执行它,而是可以在构造函数级别强制执行它,并使SolverA和SolverB都必须将XML字符串作为构造函数的一部分.
但是,如果XML字符串不好怎么办?然后你会在构造函数中得到一个错误状态,这也是一个禁忌.所以我使用工厂模式处理这个问题:
class SolverFactory;
class BaseSolver
{
public:
virtual void solve() = 0;
protected:
virtual int ReadXML(std::string xml) = 0;
friend class SolverFactory;
};
class A : public BaseSolver
{
public:
virtual void solve() {std::cout << "A" << std::endl;}
protected:
A(){}
virtual int ReadXML(std::string xml) {return 0;}
friend class SolverFactory;
};
class B : public BaseSolver
{
public:
virtual void solve() {std::cout << "B" << std::endl;}
protected:
B(){}
virtual int ReadXML(std::string xml) {return 0;}
friend class SolverFactory;
};
class SolverFactory
{
public:
static BaseSolver* MakeSolver(std::string xml)
{
BaseSolver* ret = NULL;
if (xml=="A")
{
ret = new A();
}
else if (xml=="B")
{
ret = new B();
}
else
{
return ret;
}
int err = ret->ReadXML(xml);
if (err)
{
delete ret;
ret = NULL;
}
return ret;
}
};
我没有在这里放任何实际的XML处理,因为我很懒,但你可以让工厂从主标签中获取类型,然后传递其余的节点.这种方法确保了很好的封装,可以捕获错误xml文件,并安全地分离您尝试获取的行为.它还只将危险函数(默认构造函数和ReadXMLFile)暴露给SolverFactory,在那里你(据说)知道你在做什么.
编辑:回答问题
你说的问题是“我有一个A型B和C,如果是B,我想设置”b“设置,如果是C,我想设置”c“设置”.
利用多态性,你说“我有一个B型和B型.我告诉他们得到他们的设置.”
有几种方法可以做到这一点.如果你不介意用类修改你的IO,你可以简单地公开方法:
class BaseSolver
{
public:
virtual void GetSettingsFromCommandLine() = 0;
};
然后为每个类创建单独的方法.
如果你想分开创建它们,那么你想要的是io中的多态性.所以这样暴露它:
class PolymorphicIO
{
public:
virtual const BaseSolver& get_base_solver() const = 0;
virtual void DoSettingIO() = 0;
};
一个例子
class BaseSolverBIO : PolymorphicIO
{
public:
virtual const BaseSolver& get_base_solver() const {return b;}
virtual void DoSettingIO() { char setting = get_char(); b.set_b(setting);}
private:
BaseSolverB b;
};
乍一看,这似乎是很多代码(我们已经将类的数量增加了一倍,并且可能需要为BaseSolver和IO接口提供工厂类).为什么这样?
这是可扩展性/可维护性的问题.让我们说你已经想出了一个你想要添加的新解算器(D).如果您使用动态强制转换,则必须找到*中的所有位置并添加新的case语句.如果只有一个地方,那么这很容易,但如果它是10个地方,你很容易忘记一个,很难追查.相反,使用此方法,您将拥有一个单独的类,该类具有解算器的所有特定IO功能.
让我们想一想当解算器的数量增长时,那些dynamic_cast检查会发生什么.你已经和一个庞大的团队一起维护这个软件多年了,并且假设你已经提出了字母Z之前的求解器.每个if-else语句现在都是数百 – 几千行:如果你在O中有错误你必须滚动浏览AM只是为了找到错误.此外,使用多态的开销是不变的,而反射只是增长,增长和增长.
这样做的最后一个好处是,如果你有一个BB类:公共B.你可能拥有B的所有旧设置,并希望保留它们,只是让它更大一些.使用此模型,您可以扩展IO类以及io for BB并重用该代码.