在c ++中的has-a关系中是否有良好的接口替换

won*_*dra 6 c++ oop

我正面临着GUI设计中的OOP问题,但让我用动物示例来说明它.让我们有以下设置:

  • 有一个基类动物
  • 任何派生类都可以有一个牙齿
  • 每一只牙齿的动物都可以咬()<=>没有牙齿的动物不能咬()
  • 每个动物Bite()都是一样的(在Teeth类中有默认实现)

动物有一个牙齿是很自然的,但是现在我需要像接触这样关系.例如,如果我有一个动物矢量,如果可以的话我怎么能做每个Bite()?

std::vector<Animal *> animals;
animals.push_back(new dog());
animals.push_back(new fly());
animals.push_back(new cat());

void Unleash_the_hounds(std::vector<Animal *> animals) 
{
    //bite if you can!
}
Run Code Online (Sandbox Code Playgroud)

我想出了几个解决方案,但似乎没有一个完全适合:

1.)每个使用Teeth的类都实现了接口IBiting.但是,这个解决方案引入了大量的代码重复,我需要在每个类中"实现"Bite():

class Cat : public Animal, public IBiting {
    Teeth teeth;
public:
    virtual void Bite() { teeth.Bite(); }
}
Run Code Online (Sandbox Code Playgroud)

2.)给每一只动物的牙齿,但只允许一些人使用它们.注意:语法可能是错误的 - 它只是说明

class Animal{
    static cosnt bool canBite = false;
    Teeth teeth;
public:
    void Bite() { this->canBite ? teeth.Bite() : return; }
}

class Cat {
    static cosnt bool canBite = true;
}
Run Code Online (Sandbox Code Playgroud)

3.)更多继承 - 创建类BitingAnimal并派生它.嗯,这可行,但如果我需要衍生(非)飞行动物,其中一些有牙齿.

class Animal{}
class BitingAnimal : public Animal {
    Teeth teeth;
}
Run Code Online (Sandbox Code Playgroud)

并用作BitingAnimal.teeth.Bite()

4.)多重继承.这通常是不鼓励的,而且在大多数语言中都是不可能的,而且Cat对于牙齿来说是不合逻辑的.

class Cat : public Animal, public Teeth {
}
Run Code Online (Sandbox Code Playgroud)

5.)可以咬人的类的枚举 - 仅仅通过它的声音很奇怪.


或者我只是过于复杂并错过了重要的事情?

fir*_*rda 1

1)接口很好,你可以这样添加默认实现:

class IBiting { public virtual void bite() = 0 };
class HasTeeth, public IBiting { Teeth teeth; public:
    virtual void bite() override { teeth.bite(); } };

for(Animal* a: animals) {
    IBiting* it = dynamic_cast<IBiting*>(a);
    if(it) it->bite(); }
Run Code Online (Sandbox Code Playgroud)

1b) ...您也可以完全删除该接口并仅使用HasTeeth

class HasTeeth { Teeth teeth; public:
    void bite() { teeth.bite(); } };

for(Animal* a: animals) {
    HasTeeth* it = dynamic_cast<HasTeeth*>(a);
    if(it) it->bite(); }
Run Code Online (Sandbox Code Playgroud)

2)Animal如果不想使用RTTI/,可以使用bloating dynamic_cast您可以virtual void bite()在 Animal 上使用空实现并稍后覆盖它(一旦添加Teeth)。如果您坚持不使用 RTTI,则无需编写太多代码,但如果可以使用dynamic_cast,为什么不使用它呢?

编辑:Vaughn Cato的答案非常适合这个 -动物中的虚拟/抽象teethPtr()(或),具有诸如 之类的快捷方式。适合嵌入式世界(微芯片),但对于 PC,我仍然更喜欢.getTeeth()biteIfYouCan()dynamic_cast

3)虚拟继承可以帮助BitingAnimal我们FlyingAnimal

class BitingAnimal: public virtual Animal {
Teeth teeth; public void bite() { teeth.bite(); } };
class FlyingAnimal: public virtual Animal {
Wings wings; public void fly() { wings.fly(); } };
class FlyingBitingAnimal: /*public virtual Animal, */
public FlyingAnimal, public BitingAnimal {};
Run Code Online (Sandbox Code Playgroud)

4) 加入AnimalandTeeth没有任何意义,除非你完全删除并用orTeeth替换它。然后它就变成了我的1bHasTeethCanBite

5) 该枚举是 2 的另一个版本 - 膨胀动物。不好。 但这导致我选择不使用dynamic_cast:你可以通过功能(枚举或动物上的标志 - bool can_bite)来模仿它,它可以告诉你,强制转换是安全的。然后您可以使用多重/虚拟继承来模仿dynamic_cast(首先检查功能,然后进行强制转换)。

编辑: 沃恩·卡托(Vaughn Cato)teethPtr()也与此匹配(如果你有的话,请告诉我你可以用来咬的牙齿)并且不需要石膏。


回复评论:

简而言之:尝试命名一个功能(能力、做某事的能力、提供某事的能力)。

长答案:您需要TeethandHasTeethor singleCanBite?您的第四个解决方案在原则上还不错,但在命名和其他可能性方面。这一切都是假设的。接口在其他语言中是众所周知的(单继承+接口),HasTeeth类似于 C#IListSource中的IList GetList()andbool ContainsList(用于擦除),其中bite()不直接存在,但可以通过扩展方法添加:

static IEnumerator GetEnumerator(this IListSource it) {
    if(!it.ContainsList) yield break;
    foreach(object o in it.GetList()) yield return o; }
Run Code Online (Sandbox Code Playgroud)

在这里您可以看到,我使用 C# 扩展方法实现了与 C++ 多重继承相同的目标。名称是is-a form - SourceOf。您能与我们分享您的 GUI 中的真实姓名吗?

C++ 中的示例是iostream = istream + ostream,具有来自 ios 的虚拟继承。是——命名。