C++ - 过度使用虚拟方法?

Can*_*ner 5 c++ oop

最近我得到了一个任务,我必须实现类似于以下内容:

有些动物具有某些属性,例如:

DOG1:名称:tery,颜色:白色,喜欢的饮料:葡萄汁
DOG2:名称:奇瓦,颜色:黑色,最爱的饮料:柠檬水
Bird1:名称:翠儿,canfly:是的,cansing:没有
BIRD2:名称:招架,canfly:不,cansing:是的

如何使用OOP实践有效地在C++中实现这一目标?

我做了这样的事情:

class Animal {  
    Animal(...);  
    ...  
    public String getName() const;  
    public void setName(string s);  
    ...  
    private:  
    String name;  
}  

class Bird : public Animal {  
    Bird(...);  

    public bool canFly() const;  
    public void setCanFly(bool b);  
    ...

    private:  
    bool canFly;  
    bool canSing;  
}  

class Dog : public Animal {  
    ...  
}  
Run Code Online (Sandbox Code Playgroud)

这个实现的问题是我不能从多态性中受益:

Animal* p = new Anima(...);  
...  
p->canFly();  
Run Code Online (Sandbox Code Playgroud)

我必须使用铸造:

((Bird*)p)->canFly();  
Run Code Online (Sandbox Code Playgroud)

最后我被批评没有使用在基类的虚函数,并使用强制转换,而不是面向对象的.

但在我看来,在这里使用虚函数是没有意义的,因为getName()应该在基类中以避免同一方法的多个实现.例如,canFly不是狗的有效财产.

然后,我必须为彼此(未来)动物定义一些荒谬的东西,这些动物也从基类继承,这会产生不必要的开销:

bool Dog::canFly () const {
return false;
}
Run Code Online (Sandbox Code Playgroud)

谁在这里,我没有得到多态的基本原理?

Wil*_*ean 7

当然'canFly'是狗的有效属性,它只会返回false.

如果只在需要时才实现canFly,那就毫无意义了.在你的例子中,当你将c风格的案例交给飞行动物时,你已经投入了动物的类型,此时你根本不需要调用canFly,因为你已经知道了答案.

如果你真的不想在大量非飞行动物中实现canFly,那么实现虚拟bool canFly()const {return false; 在您的基类中,只需在飞行动物中覆盖它.

我认为这只是一个人为的"家庭作业"问题,所以答案也必然看起来很人为,但涉及大量演员对象类型的风格在实际工作中确实会成为坏消息.


Ski*_*izz 7

好吧,你不需要一个基类.考虑一下:

Animal
  |
  |--Flying Animal
  |        |---Bird
  |
  |--Non Flying Animal
           |---Dog
Run Code Online (Sandbox Code Playgroud)

哪里:

class Animal
{
public:
  virtual bool CanFly () = 0;
  String Name ();
};

class Flying : public Animal
{
public:
  virtual bool CanFly () { return true; }
};

class NonFlying : public Animal
{
public:
  virtual bool CanFly () { return false; }
};

class Bird : public Flying
{
};

class Dog : public NonFlying
{
};
Run Code Online (Sandbox Code Playgroud)

还有许多其他方法可以做到这一点,甚至使用组合而不是继承.

编辑:组成.具有层次结构,层次结构中的每个级别代表较小的成员组(狗比动物少)表示如何将所有可能类型的集合划分为层次结构的问题.正如拉格巴尔在评论中指出的那样,有些鸟儿无法飞翔.

因此,不是创建复杂的树,而是拥有一棵简单的树(或没有树),并且每只动物都包含该动物的特征列表:

class Animal
{
public:
   String Name ();
   List <Characteristic> Characteristics ();
};

class Characteristic
{
public:
   String Name ();
};

class CanFly : public Characteristic
{
public:
  bool CanFly ();
};

class Legs : public Characteristic
{
public:
  int NumberOfLegs ();
};
Run Code Online (Sandbox Code Playgroud)

然后,创造一条狗:

Animal *CreateDog ()
{
  Animal *dog = new Animal;
  dog->Characteristics ()->Add (new CanFly (false));
  dog->Characteristics ()->Add (new NumberOfLegs (4));
  return dog;
}
Run Code Online (Sandbox Code Playgroud)

并创造一只鸟:

Animal *CreateBird ()
{
  Animal *bird = new Animal;
  bird->Characteristics ()->Add (new CanFly (true));
  bird->Characteristics ()->Add (new NumberOfLegs (2));
  return bird;
}
Run Code Online (Sandbox Code Playgroud)

这有两个好处:

  1. 您可以扩展它以添加所需的任何特征.
  2. 您可以从数据驱动动物的创建,而不是硬编码.

如果您选择的语言支持反射,那么搜索特征列表非常简单.在非反射语言中,您需要实现某种方式来识别每个特征.

  • 这个规模如何?您还需要跟踪canSing.也许以后,canDance,canBurrow,canBite等等.你会为每个添加一个基类吗? (2认同)

Jam*_*lis 5

为解决技术问题,这是错误的:

((Bird*)p)->canFly(); 
Run Code Online (Sandbox Code Playgroud)

这个C风格的演员表演了一个static_cast; 如果p指向a Dog,则演员表会成功,但结果将不正确.糟糕的事情发生了.

当您不知道指向对象的派生类型最多并且您没有某种方法通过基类指针确定其类型时,您需要使用dynamic_cast:

if (Bird* bp = dynamic_cast<Bird*>(p)) {
    // p points to a Bird
}
else {
    // p points to something else
}
Run Code Online (Sandbox Code Playgroud)

dynamic_cast 如果转换失败,则返回空指针,允许您检查对象的类型.


为了解决设计问题,这取决于.在真实世界的软件中,您不能总是在基类中具有定义每个可能的派生类的行为的虚函数.这是不可能的.有时需要dynamic_cast派生类能够调用未在基类中声明的函数.

施放可能不是在这个非常简单的情况下,必要的,但如果你要考虑一个更完整的类层次结构定义了动物王国,你会发现,你要么需要在功能数量庞大Animal的基类,或者你将不得不在某些时候使用演员表.