在C ++物理模拟中,我有一个名为Circle和Square的类。这些是形状,并有一个名为push()的方法,它对它施加力。然后是Circle的一个特例,称之为SpecialCircle,其中push()应该表现出略微不同的属性。但事实上,还有SpecialSquare()应该具有相同的力属性。所以我想要一个名为Shape的抽象基类来处理Circles和Squares,但是我还想要一个名为Special的抽象基类,它将特殊属性应用于force()。
设计此类结构的最佳方法是什么?
到目前为止,我已经:
class Shape {
virtual void push();
};
class Circle : public Shape {};
class Square : public Shape {};
class Special {
virtual void push();
};
class SpecialCircle : public Circle, Special {};
class SpecialSquare : public Square, Special {};
当然,上面不会编译,因为Special :: push()和Shape :: push()冲突。我得到了“错误:请求成员'推送'是模棱两可的”,正如预期的那样。
如何重新组织我的类结构,以便Circle和Square可以相互共享某些属性,但是SpecialCircle和SpecialSquare仍然可以继承Shape,还可以从Special继承修改后的功能?
感谢。
ps。,这是钻石继承问题吗?
答案 0 :(得分:9)
另一种解决方案(它可能适合您的需求,也可能不适合您的需求,具体取决于您的实施细节):
相反,您可以:
首选聚合继承。这是Bridge pattern的应用。这个策略在Square,SpecialSquare,Circle和SpecialCircle方面的优势在于明天你必须添加Rectangle,Hexagon等等,对于你添加的每个形状,你必须实现 two 类(重复的代码是邪恶的);在我看来,这是Bridge解决的真正问题。
答案 1 :(得分:3)
据说软件中的每个问题都可以通过添加额外的间接层来解决。
Herb Sutter有一篇关于如何解决问题的优秀文章:Multiple Inheritance - Part III
简而言之,您使用中间类来“重命名”虚拟函数。正如赫伯所说:
重命名虚拟功能
如果两个继承的函数具有不同的签名,则没有问题:我们将像往常一样独立地覆盖它们。因此,诀窍是以某种方式改变两个继承函数中至少一个的签名。
更改基类函数签名的方法是创建一个从基类派生的中间类,声明一个新的虚函数,并覆盖继承的版本以调用新函数
以下是使用您的课程的一个很长的例子:
class Shape {
public:
virtual void push() = 0;
};
class Circle : public Shape
{
public:
void push() {
printf( "Circle::push()\n");
}
};
class Square : public Shape
{
public:
void push() {
printf( "Square::push()\n");
}
};
class Special {
public:
virtual void push() = 0;
};
class Circle2: public Circle
{
public:
virtual void pushCircle() = 0;
void push() {
pushCircle();
}
};
class Square2: public Square
{
public:
virtual void pushSquare() = 0;
void push() {
pushSquare();
}
};
class Special2 : public Special
{
public:
virtual void pushSpecial() = 0;
void push() {
pushSpecial();
}
};
class SpecialCircle : public Circle2, public Special2
{
public:
void pushSpecial() {
printf( "SpecialCircle::pushSpecial()\n");
}
void pushCircle() {
printf( "SpecialCircle::pushCircle()\n");
}
};
class SpecialSquare : public Square2, public Special2
{
public:
void pushSpecial() {
printf( "SpecialSquare::pushSpecial()\n");
}
void pushSquare() {
printf( "SpecialSquare::pushSquare()\n");
}
};
int main( int argc, char* argv[])
{
SpecialCircle sc;
SpecialSquare ss;
// sc.push(); // can't be called - ambiguous
// ss.push();
sc.pushCircle();
ss.pushSquare();
Circle* pCircle = ≻
pCircle->push();
Square* pSquare = &ss;
pSquare->push();
Special* pSpecial = ≻
pSpecial->push();
pSpecial = &ss;
pSpecial->push();
return 0;
}
答案 2 :(得分:2)
不是通过继承来考虑代码重用,而是使用mixins将为您提供所需的代码重用,而不会出现多重继承问题。
如果您不熟悉该技术,请在SO或Google上进行搜索。确保搜索“mixin”和“Curiously Recurring Template Pattern”。有很多很棒的文章可以帮助你入门。
答案 3 :(得分:1)
如果必须使用相同的方法从多个接口继承,编译器无法判断您要调用哪个接口,可以通过覆盖此类方法并调用所需的方法来解决此问题。
class SpecialCircle : public Circle, Special {
public:
virtual void push() { Special::push(); }
};
class SpecialSquare : public Square, Special {
public:
virtual void push() { Special::push(); }
};
但在这种情况下,我认为正确的OO方法是将Federico Ramponi建议的推送行为分解出来。
答案 4 :(得分:0)
来自Shape和SpecialCircle的SpecialShape和来自SpecialShape的SpecialSquare。
答案 5 :(得分:0)
那么,如果特殊圆和普通圆都可以施加力,并且特殊圆有另一种方法应用特殊力,为什么不有两个接口和两个方法?
struct Applicable {
virtual ~Applicable() { }
// if it applies force, better be explicit with naming it.
virtual void applyForce() = 0;
};
struct SpecialApplicable {
virtual ~SpecialApplicable() { }
virtual void applySpecialForce() = 0;
};
struct Shape {
virtual ~Shape() { }
Size getSize();
Point getPosition();
// ...
};
struct Circle : Shape, Applicable {
virtual void applyForce() { /* ... */ }
}
struct SpecialCircle : Circle, SpecialApplicable {
virtual void applySpecialForce() { /* .... */ }
};
如果同时存在一个特殊的和普通的apply方法(类的名称 - SpecialCircle
- 建议)没有意义,那么为什么不这样做呢:
struct Circle : Shape, Applicable {
virtual void applyForce() { /* ... */ }
}
struct SpecialCircle : Circle {
// applies force, but specially
virtual void applyForce() { /* .... */ }
};
您也可以将applyForce
放入Shape类。它还取决于使用这些类的环境。在任何情况下,你真正应该避免的是在两个基本类中出现两个基本格的相同方法。因为那不可避免会导致这种模棱两可的问题。 diamond 继承是在使用虚拟继承时。我相信stackoverflow上有其他好的答案可以解释这一点。它不适用于您的问题,因为出现歧义是因为该方法出现在两个不同类型的基类子对象中。 (它只解决了基类具有相同类型的情况。在这种情况下,它将合并基类,并且只包含一个基类子对象 - 由虚拟继承继承)