我今天遇到了一个问题found here,它为我提出了这个问题。
这是我所得到的伪代码示例:
class Car{
public:
virtual int goFast() = 0;
};
class FordFocus : public Car {
public:
int goFast(){
return 35;
};
};
class Lamborghini : public Car {
bool roof;
public:
int goFast(){
return -1/0; // crash
};
void retractTheRoof(){
roof = 0;
};
};
class RichGuy {
vector<Car *> cars;
public:
void goDrive() {
for(int i = 0; i < cars.size(); ++i) {
if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
lambo->retractTheRoof();
};
goFast();
};
};
};
在示例中,有一个RichGuy
类。 Richguy
仅在单个向量中跟踪他的Cars
。因为他有这么多Cars
,根据它们是FordFocus
还是Lamborghini
来跟踪它们会太麻烦。然而,他有一个可伸缩屋顶的唯一类型的汽车是兰博。为了retractTheRoof()
,RichGuy
现在必须确定他所拥有的Car
是否确实是Lamboghini
,然后向下转发以执行此功能。
基于这个例子,是否选择了良好的设计?或者它是否违反了多态的目的,假设目的是允许派生类定义自己的行为,并为像RichGuy
这样的类提供通用接口?如果是这样,是否有更好的方法允许retractTheRoof()
(或至少它的效果)等函数可供RichGuy
使用?
答案 0 :(得分:14)
现在,如果有多种类型的汽车可以伸缩,那么这类汽车就是CarA
,CarB
和CarC
(除Lamborghini
之外),那你打算写这个:
if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
lambo->retractTheRoof();
}
else if(CarA * pCarA = dynamic_cast<CarA*>(cars[i])) {
pCarA->retractTheRoof();
}
else if(CarB * pCarB = dynamic_cast<CarB*>(cars[i])) {
pCarB->retractTheRoof();
}
else if(CarC * pCarC = dynamic_cast<CarC*>(cars[i])) {
pCarC->retractTheRoof();
}
因此,在这种情况下更好的设计是:添加一个名为IRetractable
的接口并从中派生出来:
struct IRetractable
{
virtual void retractTheRoof() = 0;
};
class Lamborghini : public Car, public IRetractable {
//...
};
class CarA : public Car, public IRetractable {
//...
};
class CarB : public Car, public IRetractable {
//...
};
class CarC : public Car, public IRetractable {
//...
};
然后你可以简单地写一下:
if(IRetractable *retractable = dynamic_cast<IRetractable *>(cars[i]))
{
retractable->retractTheRoof(); //Call polymorphically!
}
冷却?不是吗?
在线演示:http://www.ideone.com/1vVId
当然,这仍然使用dynamic_cast
,但重要的一点这里只是你正在玩 interfaces ,不需要提及具体课程随处可见。换句话说,设计仍然尽可能地使用 runtime-polymorphism 。这是Design Patterns:
“编程到'接口',而不是'实现'。” (Gang of Four 1995:18)
另外,请看:
其他重要的一点是你必须使Car
(基类)的析构函数虚拟化:
class Car{
public:
virtual ~Car() {} //important : virtual destructor
virtual int goFast() = 0;
};
它很重要,因为你正在维护一个Car*
的向量,这意味着,稍后你想要通过基类指针删除实例,你需要使~Car()
虚拟析构函数,否则delete car[i]
将调用未定义的行为。
答案 1 :(得分:7)
是的,这通常是更好的设计案例。如果对所有派生类有意义,则只应在层次结构中引入虚函数。
但是,你的MODEL枚举完全没用 - 这就是dynamic_cast
实际 。
if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
lambo->retractTheRoof();
}
答案 2 :(得分:4)
如果你关心屋顶的可伸缩性,你可以在继承链中的ConvertibleCar
和Car
之间有一个Lamborghini
抽象基类:
class Car {
public :
virtual int goFast() = 0;
};
class ConvertibleCar : public virtual Car {
public :
virtual void retractTheRoof() = 0;
};
class FordFocus : public Car {
public :
int goFast() { return 35; };
};
class Lamborghini : public ConvertibleCar {
bool roof;
public :
int goFast() { return -1/0; /* crash */ };
void retractTheRoof() { roof = 0; };
};
如果RichGuy
课程无法分别跟踪所有不同类型的汽车,您仍然可以使用dynamic_cast
来确定某辆汽车是否属于某种类型:
ConvertibleCar* convertible = dynamic_cast<ConvertibleCar*>(cars[i]);
if (convertible) {
convertible->retractTheRoof();
};
请注意,这可以很好地适应不同的车型(ConvertibleCar
,AllTerrainCar
,SportsCar
,...),其中同一辆车可以从0或更多这些类型继承。 Lamborghini
可能来自ConvertibleCar
和SportsCar
:
class Lamborghini : public SportsCar, public ConvertibleCar {
// ...
};
答案 3 :(得分:3)
而不是在这个模块中检查“兰博基尼”,因为我知道兰博基尼并不是唯一一家生产带有可伸缩屋顶的汽车的汽车制造商。
然后你根本不需要动态投射。这就是它应该如何完成的。
class Car{
public:
virtual int goFast() = 0;
virtual void retractTheRoof(){ /*no-op*/}; // default
};
class Lamborghini : public Car {
bool roof;
public:
int goFast(){
return -1/0; // crash
};
void retractTheRoof(){ roof = 0;}; // specific
};
然后在代码而不是
if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
lambo->retractTheRoof();
}
DO
cars[i]->retractTheRoof();
就是这样。