我正面临着GUI设计中的OOP问题,但让我用动物示例来说明它.让我们有以下设置:
动物有一个牙齿是很自然的,但是现在我需要像接触这样的关系.例如,如果我有一个动物矢量,如果可以的话我怎么能做每个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.)可以咬人的类的枚举 - 仅仅通过它的声音很奇怪.
或者我只是过于复杂并错过了重要的事情?
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替换它。然后它就变成了我的1b。HasTeethCanBite
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 的虚拟继承。又是——命名。