Is there any good substitution for an interface in a has-a relationship in C ++

I ran into the problem of OOP in GUI design, but let me illustrate it with an example for animals. Allows you to perform the following settings:

  • there is a base class Animal
  • any derived class can have - Teeth
  • each animal with teeth can bite () <=> animals without teeth cannot bite ()
  • each animal Bite () is the same (there is a default implementation in the Teeth class)

Naturally, the animal has teeth, but now I need something like an interface for the "eat" relationship. For example, if I have a vector of animals, how can I do each Bite () if they can?

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! } 

I came up with several solutions, but none of them look perfect:

1.) Each class with Teeth also implements the IBiting interface. This solution, however, introduces a lot of duplicate code, I will need to "implement" Bite () in each class:

 class Cat : public Animal, public IBiting { Teeth teeth; public: virtual void Bite() { teeth.Bite(); } } 

2.) Give each animal teeth, but only allow some to use them. Note: the syntax may be incorrect - this is just an illustration.

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

3.) More inheritance - create a BitingAnimal class and output it. Well, that might work, but what if I needed to breed (non) flying animals, some of them have teeth.

 class Animal{} class BitingAnimal : public Animal { Teeth teeth; } 

and use as BitingAnimal.teeth.Bite ()

4.) multiple inheritance. This is often discouraging and impossible in most languages, plus it is illogical for Cat to be teeth.

 class Cat : public Animal, public Teeth { } 

5.) Enumerating classes that can bite is strange only by sound.


Or am I just complicating it too much and missing something important?

+6
source share
6 answers

1) The interface is good, and you can add a default implementation this way:

 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(); } 

1b) ... you can completely remove the interface and use only HasTeeth :

 class HasTeeth { Teeth teeth; public: void bite() { teeth.bite(); } }; for(Animal* a: animals) { HasTeeth* it = dynamic_cast<HasTeeth*>(a); if(it) it->bite(); } 

2) Animal explosion can be used if you do not want to use RTTI / dynamic_cast . . You can make virtual void bite() with an empty implementation on Animal and override it later (adding Teeth once). Not much code if you insist on not using RTTI, but if you can use dynamic_cast , why not use it?

EDIT: The answer from Vaughn Cato is perfect for this - virtual / abstract teethPtr() (or getTeeth() ) in Animal with short abbreviations like biteIfYouCan() . Good for the embedded world (microchips), but for PC, I still prefer dynamic_cast .

3) Virtual inheritance can help us with BitingAnimal vs. 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 {}; 

4) The combination of Animal and Teeth does not make sense if you do not completely remove Teeth and replace it with HasTeeth or CanBite . Then he becomes my 1b.

5) This listing is another version 2 - a bloated animal. Not good. But this leads me to an alternative that does not use dynamic_cast: you can simulate it using features (this enumeration or flags on animals - bool can_bite ) that can tell you that the cast is safe. Then you can use multiple / virtual inheritance to simulate dynamic_cast (first check the validation capabilities, then click below).

EDIT: Vaughn Cato teethPtr() also matches this ( show me teeth that can be bitten if you have them ) and does not need to be broadcast.


Reply to comment:

In short: try to name a function (ability, ability to do something to provide something).

Long answer: Do you need Teeth and HasTeeth or single CanBite ? Your fourth decision is not bad in principle, but in the name and, therefore, in other possibilities. All of this was hypothetical. Interfaces are well known in other languages ​​(single inheritance + interfaces), HasTeeth is something like C # IListSource with IList GetList() and bool ContainsList (for erasing), where bite() does not exist directly, but can be added using the extension method :

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

Here you can see that I achieved the same goal with the C # extension method as with C ++ multiple inheritance. The name is-a is SourceOf . Can you share real names with us from your GUI?

An example from C ++ would be iostream = istream + ostream with virtual inheritance from ios. Again is-a .

+1
source

An alternative that you did not talk about is simply providing an abstraction for the teeth, but the implementation of biting in the base class. This reduces duplication because derived classes only need to specify how to access the teeth, not how to bite. By returning the pointer to the teeth, we can enable the null pointer to indicate that the animal has no teeth. Here is an example:

 #include <vector> struct Teeth { void bite() { } }; struct Animal { virtual Teeth *teethPtr() = 0; void biteIfYouCan() { if (teethPtr()) teethPtr()->bite(); } }; struct Dog : Animal { Teeth teeth; Teeth *teethPtr() override { return &teeth; } }; struct Fish : Animal { Teeth *teethPtr() override { return nullptr; } }; int main() { Dog dog; Fish fish; std::vector<Animal *> animals {&dog,&fish}; for (auto animal_ptr : animals) { animal_ptr->biteIfYouCan(); } } 
+2
source

Here are my comments on your decisions:

  • I agree with you that this will lead to a lot of code duplication. Not cool.
  • This is clearly not the best way. Using the canBite flag is a sign that this can be done better.
  • Again, poor design - calling BitingAnimal.teeth.Bite() is a violation of the Law of Demeter . Also, as you described - what if an Animal that wasn't BittingAnimal wanted to bite?
  • As Mare Infinitus commented, in this approach your Cat is Teeth . This is not entirely correct.
  • Listing classes that might bite - you're right. This is strange:)

I would suggest the following approach: create an IBiting interface and its implementation of Biting in a mixin -like way:

 class Bitting : public IBiting { Teeth teeth; public: virtual void Bite() { teeth.Bite(); } } 

then every class that can bite inherits from Biting "mixin":

 class Cat : public Animal, public Biting { } 

It will be multiple inheritance, of course, but since Biting only implements the "Biting" functionality, it will not be so bad (no Problem with diamonds ).

+1
source

The question comes down to what should happen if you call animal->bite() and that the animal has no teeth and cannot bite. One answer may be that all animals can bite. For cats, it has an effect due to teeth, while other animals, such as butterflies, can bite, but nothing happens.

 class Animal{ public: virtual void bite(){} }; class Cat : public Animal{ Teeth teeth; void bite() override{ teeth.bite(); } }; class Butterfly : public Animal{ }; 

In this approach, you need to write for each type of animal how it bites, if possible. This gets a little trickier if you need other attributes like scream() and fly() . Yes, cats can fly in this model, just nothing happens when they do, and butterflies can scream with zero.

Since there are many animals that have teeth, and they all bite in the same way as you could add a few specialized animals.

 class BitingWithTeethAnimal : public Animal{ Teeth teeth; void bite() override{ teeth.bite(); } }; class Cat : public BitingWithTeethAnimal{ }; class Butterfly : public FlyingWithWingsAnimal{ }; 

Ideally, you could say something like class Pterodactyl : public BitingWithTeeth, FlyingWithWings, ScreamingWithVoice, Animal; without shaking the Animal the monster class, but this is not possible in my implementation. The positive thing is that you only implement what you need, and you never get confused when an animal can bite with its teeth, as well as with its beak, because the implementation is performed in one function and is not divided into different classes.

Pterodactyl

+1
source

This answer is no longer valid as soon as we clarified that:

each animal with teeth can bite () <=> animals without teeth cannot bite ()

This leads to less separation. There are better answers, but I recovered this to show my thoughts on design:


Original:

 class Animal { public: virtual ~Animal() {} /* for RTTI */ }; class IBite { virtual void Bite() = 0; }; // interface class Teeth: public IBite { public: void Bite() { ... } }; // implementation class HasTeeth { protected: Teeth teeth; }; // part of thought process class BiteWithTeeth: public HasTeeth, public IBite { // interface public: void Bite() { teeth.Bite(); } }; // +implementation class Cat: public Animal, public BiteWithTeeth {}; // final class class Unknown: public Animal, public HashTeeth {}; // but no IBite - not allowed 

The above order will make you think why. It includes using dynamic_cast to find the interface (the solution will be different if you do not insist on using it). I prefer to separate things that are not connected and find the smallest things (classes / interfaces) that are really connected (otherwise you will do it later when you find that you need it - if you ever need to bite your teeth from your teeth).

 class AnimalBitingWithTeeth: public Animal, public BiteWithTeeth {}; class AnimalWithTeethButNotBiting: public Animal, public HashTeeth {}; 

We need Animal as a base class and IBite (or IBiting ) to mark some feature. Other classes allow you to implement this function (for example, BiteWithTeeth ), but also allow other implementations to be performed.

An important question: can we have a chain in which IBite will be enabled, but we want to disable it? We can redefine (virtual) Bite empty or add virtual bool CanBite() if we need to ask such a question (instead of biting if you can). To create a good design, it is difficult to find the right questions (and functions) in OOP.


See my second answer for a solution - multiple / virtual inheritance.

0
source

It’s the smell of code to put things in the same container that don’t share anything. Request for the correct container instead of requesting a class of its capabilities.

If you really want one creation instance to use a helper class. But try using interfaces for classes that have something in common . Imagine if you have 5,000 species of animals, and two of them can bite. Are you sure you want to look at each of them to check each of them for teeth? You already know that you have only two dogs at the time of their acquisition.

 #include <iostream> #include <vector> #include <memory> using namespace std; struct Animal { virtual ~Animal() {} }; // used to tag elements struct Biting : public Animal { virtual void bite() = 0; }; struct Flying : public Animal { virtual void fly() = 0; }; struct Bird : public Flying { virtual void fly() override { cout << "Fly\n"; } }; struct Dog : public Biting { virtual void bite() override { cout << "Bite\n"; } }; struct World { void populate(Animal *animal) { Biting *b = dynamic_cast<Biting *>(animal); if (b != nullptr) { Biters.push_back(b); return; } Flying *f = dynamic_cast<Flying *>(animal); if (f != nullptr) { Flyers.push_back(f); return; } } std::vector<Biting *> Biters; std::vector<Flying *> Flyers; }; class Schaefer : public Dog { }; class Pitbull : public Dog { }; class KingFisher : public Bird { }; class WoodPecker : public Bird { }; int main(int argc, char **argv) { World w; Schaefer s; Pitbull p; KingFisher k; WoodPecker wp; w.populate(&s); w.populate(&p); w.populate(&k); w.populate(&wp); for (auto &i : w.Biters) { i->bite(); } for (auto &i : w.Flyers) { i->fly(); } return 0; } 
0
source

Source: https://habr.com/ru/post/973903/


All Articles