最近,我接到了一项任务,我必须实现类似于以下内容的任务:
有些动物具有某些属性,例如:
Dog1:名称:tery,颜色:白色,fav饮料:葡萄汁
Dog2:名称:chiva,颜色:黑色,fav饮料:柠檬水
Bird1:名字:tweety,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 {
...
}
这种实现的问题在于我不能从多态性中受益:
Animal* p = new Anima(...);
...
p->canFly();
我必须使用cast:
((Bird*)p)->canFly();
最后,我被批评不在基类中使用虚函数,并使用强制转换而不是OOP。
但是在我看来,在这里使用虚函数是没有意义的,因为getName()应该在基类中以避免同一方法的多个实现。例如,canFly不是狗的有效财产。
然后,我必须为彼此(未来)动物定义一些荒谬的东西,这些动物也从基类继承,这将产生不必要的开销:
bool Dog::canFly () const {
return false;
}
谁就在这里,我没有得到多态的基本原理吗?
答案 0 :(得分:7)
当然' canFly'是狗的有效财产,它只会返回假。
如果只在需要时才实现canFly,那就毫无意义了。在你的例子中,当你对飞行动物做过你的c风格的情况时,你已经承诺了这种类型的动物,此时你真的不需要打电话canFly,因为你已经知道了答案。
如果你真的不想在大量非飞行动物中实施canFly,那么实现虚拟bool canFly()const {return false;在您的基类中,只需在飞行动物中覆盖它。
我认为这只是一个人为的作业'问题,所以答案也必然看起来很人为,但是涉及大量演员对象类型的风格在实际工作中确实会成为坏消息。
答案 1 :(得分:7)
好吧,你不需要一个基类。考虑一下:
Animal
|
|--Flying Animal
| |---Bird
|
|--Non Flying Animal
|---Dog
其中:
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
{
};
还有许多其他方法可以做到这一点,甚至使用合成而不是继承。
编辑:作文。具有层次结构,层次结构中的每个级别代表较小的成员组(狗比动物少)表示如何将所有可能类型的集合划分为层次结构的问题。正如拉格巴尔在评论中指出的,有些鸟不能飞。
因此,不是创建一个复杂的树,而是拥有一棵简单的树(或没有树),并让每只动物都包含该动物的特征列表:
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 ();
};
然后,创造一只狗:
Animal *CreateDog ()
{
Animal *dog = new Animal;
dog->Characteristics ()->Add (new CanFly (false));
dog->Characteristics ()->Add (new NumberOfLegs (4));
return dog;
}
并创造一只鸟:
Animal *CreateBird ()
{
Animal *bird = new Animal;
bird->Characteristics ()->Add (new CanFly (true));
bird->Characteristics ()->Add (new NumberOfLegs (2));
return bird;
}
这有两个好处:
如果您选择的语言支持反射,那么搜索特征列表非常简单。在非反射语言中,您需要实现某种方式来识别每个特征是什么。
答案 2 :(得分:5)
要解决技术问题,这是错误的:
((Bird*)p)->canFly();
这个C风格的演员表现static_cast
;如果p
指向Dog
,则演员表会成功,但结果将不正确。糟糕的事情发生了。
当你不知道指向对象的派生类型最多而你没有办法通过基类指针确定它的类型时,你需要使用dynamic_cast
:
if (Bird* bp = dynamic_cast<Bird*>(p)) {
// p points to a Bird
}
else {
// p points to something else
}
如果转换失败, dynamic_cast
将返回空指针,允许您检查对象的类型。
解决设计问题,取决于。在真实世界的软件中,您不能总是在基类中具有定义每个可能的派生类的行为的虚函数。这是不可能的。有时需要dynamic_cast
派生类才能调用未在基类中声明的函数。
在这个非常简单的情况下,可能没有必要使用强制转换,但如果你考虑一个更完整的类层次结构定义动物王国,你会发现你需要在{{1}中需要大量的函数基类,或者你必须在某些时候使用强制转换。
答案 3 :(得分:3)
虚拟方法只有在需要子类提供自己的实现或强制它们(纯虚拟)时才有意义。
对于canFly
和canSing
用法,,其中基类中的数据成员支持所有子类中的不变实现,使得这些get / set方法{{ 1}}对我毫无意义。
virtual
的一个更好的候选者是相应的virtual
和fly
方法,其中基类实现可能会抛出,并且只有在设置属性sing
时才会生成动物 - 特定的实现在子类中提供。
答案 4 :(得分:3)
struct Animal {
std::string name;
std::string favoriteDrink;
bool canFly;
bool canSing;
};
如果它让你开心,可以随意将get / setters包裹在成员周围。
但人们往往忘记的一件事是多态性与行为有关。它是关于使不同的类看起来相同,但表现不同。
在这个例子中,任何动物之间都没有没有不同的行为,因此制作多个类是过度的。
任何动物都不需要实际的行为。我们需要的唯一操作就是能够询问“它的名字是什么”,“它可以飞”,“它可以唱歌”(当然,“它会融合吗?”)
对于企鹅而言,所有这些操作都像对待猎犬,蓝鲸或泼妇一样有意义。行为是相同的,只有数据发生变化。所以它应该是一个类,对不同的动物有不同的实例。
因此尝试将它们分成单独的类违反了OOP的所有目标:最终故意复制代码,减少代码重用,并且使代码更少多态,而不是更多。在我的解决方案中,任何动物都是任何其他动物的直接替代品。一旦你开始搞乱不同的类和虚方法,你必须为每个新动物编写新的代码,以使它成为Animal
基类的合适实现。
如果您需要添加实际的Fly()
方法,则可能需要不同的类。对于麻雀,老鹰和蝙蝠来说,飞行的机制是不同的(虽然这取决于目标。根据应用程序正在处理的抽象级别,“飞行”例程可能只包括设置另一个{{ 1}}在某处标记,或者可能给动物一个正的非零高度,在这种情况下,相同的实现可以重复使用任何飞行动物。)
但目前,我们所需要的只是能够询问动物是否可以飞行。而 的实现是可以重复使用的。
但是,当然,从给出的任务中可以清楚地看到正确答案(“正确”定义为“当我提出问题时我预期的”是“使用大量虚拟方法所有< / em>,并给它自己的所有类“。
这只是表明你从某人那里得到的OOP狂热程度越高,他们实际上理解 OOP的几率就越低。
答案 5 :(得分:1)
在这个简单的情况下可能太多了,但是稍后您可以将所有动物保存在链表(或标准列表或数组或其他)中,然后迭代所有条目并调用基本方法来完成所有操作各种各样的东西,而不必担心演员。
想想一个简单的游戏,GameObject
是基类,方法update()
和draw()
是虚拟的。然后继承其他类,例如PlayerObject
,EnemyObject
,PowerUpObject
等
在主循环中,您可以执行以下操作:
GameObject *p = firstObject;
while(p)
{
p->update();
p = p->nextObject;
}
这将遍历所有游戏对象并调用正确的update()
方法(例如移动玩家,让动力旋转或其他任何东西),你不需要做一些特殊的投射,例如检查是否是玩家或其他什么。
答案 6 :(得分:0)
我认为你是对的。将一些动物家族可以拥有的所有可能的属性添加到基类Animal
是非常愚蠢的并且产生过多的开销。
虽然很清楚任务的目的是什么,也就是你在基类中确实有一个虚函数canFly,但我认为这是一个糟糕的设计。
答案 7 :(得分:0)
声明虚拟内容并不会阻止您在基类中实现它。
这是一种说明您应该使用最具体的实现的机制。它与派生类中的实现不同。
为什么要从狗的canFly()返回false是一个问题?有些鸟不能飞,有非鸟可以飞。
答案 8 :(得分:0)
在我看来,使用getter 和 setter方法表明面向对象设计不佳。而这个问题空间并不是特别有利于炫耀哪种良好的面向对象设计。