我有一个抽象类Parent,它有多个子节点和空白函数,用于与这些子节点中的每一个进行交互。每个孩子都会覆盖父母的职能,并以不同的方式与其他孩子互动;即Child1对interact_with(Child1),interact_with(Child2)等具有不同的实现。
在Parent中,我有一个功能interact_with(Parent foo)。每个想要与另一个孩子互动的孩子必须首先通过这个功能。到目前为止一切都很好,但是我们遇到了一个问题:在处理了一些基本逻辑之后,Child需要知道它的参数的具体类型,以便它可以继续并调用它自己的重写函数。目前我有这个:
Child1* child1 = dynamic_cast<Child1*>(foo);
Child2* child2 = dynamic_cast<Child2*>(foo);
Child3* child3 = dynamic_cast<Child3*>(foo);
if(child1 != nullptr){
interact_with(child1)
}
else if(child2 != nullptr){
interact_with(child2)
}
else if(child3 != nullptr){
interact_with(child3)
}
它有效,但它不是一个很好的解决方案。当我有这么多课程时,它会变得特别糟糕。这是否表明基础设计存在缺陷,如果是这样,我将如何改进?
编辑:澄清:我有类似的东西
//Parent is an abstract class
class Parent
{
void interact_with(Parent* foo){
//this is here because there is a lengthy code segment
//that needs to be run no matter what child interacts
//with which
//afterwards, I need to interact with whatever foo really is
}
virtual void interact_with(Child1* child){*blank*};
virtual void interact_with(Child2* child){*blank*};
virtual void interact_with(Child3) child){*blank*};
};
class Child1 : public Parent
{
virtual void interact_with(Child1* child){*do something*};
virtual void interact_with(Child2* child){*do something else*};
virtual void interact_with(Child3* child){*do something else*};
};
已经。
答案 0 :(得分:2)
警告:此解决方案将破坏您的界面。但不要担心,它们已经坏了;)
在我看来,你的设计错误如下:虽然所有的孩子都是平等的#34;你选择其中一个来负责交互并在其上调用一个方法(如果你想要3个,4个,......,N个相等的孩子(在数组中)同时进行交互,哪一个负责?)
如果在您的应用程序中所有对象都同等重要且没有对象负责交互,则应将交互移动到自由重载的二进制函数中:
void interact(Child1* a, Child1* b);
void interact(Child1* a, Child2* b);
...
void interact(Child2* a, Child1* b)
{
interact(b, a); // if order does not matter, reuse another function
}
显然,它不会解决样板代码问题,但至少它可以帮助您重新思考您的设计并找到比双重调度或投射更好的解决方案。
此外,根据函数内部结构,您可以通过使用模板函数而不是重载函数来轻松地减少写入(而不是代码大小)。
答案 1 :(得分:2)
使用双重调度的@imreal答案是正确的。但是,可以使用虚拟成员函数而不是映射和函数指针(实际上类似于编译器生成的vtable)来完成调度。
问题是单个虚函数无法解决问题,因为您确实需要双重调度(即关于两个对象的虚拟调用,而不仅仅是被调用的对象)。
请参阅以下工作示例:
#include <iostream>
class Child1;
class Child2;
class Parent
{
public:
virtual void interact_with(Parent* other) = 0;
virtual void interact_with(Child1* child) {};
virtual void interact_with(Child2* child) {};
};
class Child1 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child1 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child1 - Child2\n";
}
};
class Child2 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child2 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child2 - Child2\n";
}
};
int main()
{
Child1 c1;
Parent* p1 = &c1; // upcast to parent, from p1, we don't know the child type
Child2 c2;
Parent* p2 = &c2;
c1.interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(p2); // double virtual call to Child2 - Child1 (NB: reversed interaction)
}
输出:
Child1 - Child2
Child1 - Child2
Child2 - Child1
注意最后一个是相反的。那是因为要在参数上使用虚函数进行动态调度,我必须将this
指针与参数交换。如果这些相互作用是对称的,那就没问题。如果它们不是,那么我建议创建一个围绕最通用的包装器,再次交换this
和参数。
答案 2 :(得分:1)
使用dynamic_cast&lt;&gt;像你说的那样糟糕的设计。你应该在声明中像这样使用interact_with virtual函数。
virtual void interact_with(Parent foo);
这将使方法调用使用子类的interact_with实现而不是父类。然后,您可以替换为此而写的所有内容。
interact_with(foo);
答案 3 :(得分:1)
你想要的是double dispatch
根据您的要求,您可以采取许多方法。非常通用的是具有形式Key(类型,类型)的简单地图 - &gt;将参数类型对与要调用的函数关联的值(函数)。
在这种情况下,您需要一组void function(Parent*, Parent*)
类型的免费函数(一个用于您需要的每个组合)和一个std::unordered_map<std::pair<TypeId, TypeId>, FunctionType>
类型的地图,其中TypeId
是某种形式具有值语义的类型标识符。
然后在运行时执行调度:
if(map_.find(make_pair(type1, type2)) != map_.end())
map_[make_pair(type1, type2)](obj1, obj2);
在注册每个功能之前:
map_[make_pair(type1, type2)] = func12;
map_[make_pair(type2, type3)] = func23;
....