Наследование и интерфейсы в C++
Допустим, у меня есть полностью виртуальный/абстрактный класс INode (интерфейс), все его методы исключительно виртуальные. В последствии я хочу от него уже наследовать класс CNode, где эти методы будут реализованы. Также я хочу создать потомок-интерфейс, например INodeSpatial, тоже полностью виртуальный, который унаследует все виртуальные методы родителя + добавит какие-то свои виртуальные методы. В последствии я хочу от него наследовать CNodeSpatial где все эти методы будут реализованы. Ну и так далее..
Но тут получается следующее:
- Класс
CNode, который наледуется отINodeбудет реализовывать вирутальные методыINode - Класс
CNodeSpatial, надедуется, опять же, только от виртуальногоINodeSpatial, но не отCNode, и это значит что вCNodeSpatialмне придется описывать все то же самое, что было описано вCNode - Возникла мысль, чтобы этого не делать - наследовать
CNodeSpatialи отINodeSpatial, и отCNode(где уже реализация всех виртуальных методовINodeописана). Тогла мне придется описать только те виртуальные методы, которые есть вINodeSpatial
Но тут возникает вопрос:
Насколько это вообще хорошо и правильно? Просто получается что класс CNodeSpatial дважды наследуется от INode (поскольку INode является родителем и CNode и INodeSpatial). Так вообще можно? Не приведет ли это к проблемам в дальнейшем? Насколько такой подход соответствует "хорошей практике"?
Примеры:
Интерфейсы
class INode
{
public:
virtual void methodA() = 0;
virtual void methodB() = 0;
virtual ~INode() = default;
};
class INodeSpatial : public INode
{
public:
virtual void methodC(int foo) = 0;
virtual void methodD(int foo) = 0;
~INodeSpatial() override = default;
};
"Реальные" классы:
class CNode : public INode
{
public:
void methodA() override
{
//TODO: Implementation of INode methods in CNode
}
void methodB() override
{
//TODO: Implementation of INode methods in CNode
}
};
class CNodeSpatial : public INodeSpatial
{
public:
void methodA() override
{
//TODO: Implementation of INode methods in CNodeSpatial (already done in CNode)
}
void methodB() override
{
//TODO: Implementation of INode methods in CNodeSpatial (already done in CNode)
}
void methodC([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
void methodD([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
};
Как можно видеть, в классе CNodeSpatial приходится дублировать реализации которые уже были описаны в CNode, потому что наследование идет исключительно от виртуальных интерфейсов. Но если наследоваться И от СNode, проблема как бы исчезает:
class CNodeSpatial : public INodeSpatial, public CNode
{
public:
void methodC([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
void methodD([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
};
Но в итоге получается то класс как бы 2 раза наследуется от INode, что меня и смущает. Это нормально? Так делают? Проблем в дальнейшем не вызовет?
Ответы (2 шт):
класс как бы 2 раза наследуется от INode
Чутье вас не подвело.
Ваш пример с class CNodeSpatial : public INodeSpatial, public CNode не делает то, что вы думаете.
Класс CNodeSpatial при таком подходе абстрактный - попробуйте создать экземпляр, увидите.
Решение - CNode и INodeSpatial должны наследоваться от INode виртуально.
Можно сделать так:
class INodeSpatial : public INode
{
protected:
virtual void methodC(int foo) {}
virtual void methodD(int foo) {}
public:
~INodeSpatial() override = default;
};
Так как базовый класс абстрактный, то и производный класс останется абстрактным, поскольку не определяет чистый виртуальный метод. Но он добавляет новые, не чистые, виртуальные методы, но только защищенные, чтобы производные классы могли переопределить и могли не переопределять.
Практически INodeSpatial является типичным узловым классом для построения производных классов:
class CNode : public INodeSpatial
{
public:
void methodA() override
{
//TODO: Implementation of INode methods in CNode
}
void methodB() override
{
//TODO: Implementation of INode methods in CNode
}
};
Теперь CNode имеет в своем открытом интерфейсе только методы из INode
class CNodeSpatial : public CNode
{
public:
void methodC([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
void methodD([[maybe_unused]] int foo) override
{
//TODO: Implementation of INodeSpatial methods
}
};
Ну а этот определил еще и методы из INodeSpatial, и имена типов соответствующие иерархии.
Хочется отметить, что я полностью согласен с самым первым комментарием под вопросом(от avp). Старайтесь делать проще. Например, если нет необходимости создавать дерево иерархии(всего два класса у вас фигурируют в данном виде) так, чтобы было удобно писать рудиментарный код по ссылке на базовый класс, то стоит подумать об избавлении всего лишнего...
