Полиморфизм (или наследование) может привести к проблемам, если ваша иерархия станет слишком большой.
Например, может быть логично иметь следующие классы:
class Animal;
class Mammal : public Animal;
class Dog : public Mammal;
class Cat : public Mammal;
class Fish : public Animal;
Но если в какой-то момент вам необходимо провести различие между животными, которые могут плавать (собака, рыба), и животными, которые не умеют плавать (представьте на мгновение, что кошка не умеет плавать). Тогда было бы логичнее иметь это так:
class Animal;
class SwimmingAnimal: public Animal;
class Dog : public SwimmingAnimal;
class Fish: public SwimmingAnimal;
class NonSwimmingAnimal: public Animal;
class Cat : public NonSwimmingAnimal;
Вы можете попытаться реализовать обе иерархии, используя виртуальное наследование, но это быстро приводит к множеству проблем (одна из них также называется «проблема алмазов»).
Использование интерфейсов может решить многие из этих проблем, но с затратами.
class Animal;
class Dog : public Animal, public IMammal, public SwimmingAnimal;
class Cat : public Animal, public IMammal;
class Fish: public Animal, public SwimmingAnimal;
Поскольку классы наследуются только от интерфейсов, они не могут легко использовать одни и те же функции. Таким образом, функциональность совместного использования должна быть реализована посредством локализации. Если Dog и Fish хотят предложить обе функции «плавания», они должны предоставить метод плавания и реализовать его, перенаправив вызов в отдельный класс Swim, что-то вроде этого (это весь псевдокод):
class Dog : public Animal, public IMammal, public SwimmingAnimal
{
public:
void swim(double speed) {m_swim->swim(speed);}
private:
Swim m_swim;
}