Множественное наследование интерфейса
Рассмотрим следующий код:
class IChunk
{
public;
virtual void Method() = 0;
};
class FlatChunk : public IChunk
{
public:
void Method() override;
};
class IAudioChunk : public IChunk
{
public:
virtual AudioFormat GetFormat() const = 0;
};
class IVideoChunk : public IChunk
{
public:
virtual VideoFormat GetFormat() const = 0;
};
class SomeAudioChunk : public IAudioChunk, public FlatChunk
{
public:
AudioFormat GetFormat() const override;
};
class SomeVideoChunk : public IVideoChunk, public FlatCHunk,
{
public:
VideoFormat GetFormat() const override;
};
Это упрощенный код.
Здесь должно происходить следующее:
IChunk- это интерфейс для работы с каким-то куском данных;FlatChunk- реализация интерфейсаIChunk, которая работает с данными, хранящимися в памяти в виде сплошного массива байтов;IAudioChunk- интерфейс для работы с аудиоданными, который наследуется от интерфейса работы с данными, плюс еще кое-что, специфичное для аудио;IVideoChunk- интерфейс для работы с видеоданными, который наследуется от интерфейса работы с данными, плюс еще кое-что, специфичное для видео;SomeAudioChunk- какая-то реализация интерфейсаIAudioChunk, которая, к тому же, использует реализациюFlatChunkдля работы с данными в виде сплошной последовательности байтов;SomeVideoChunk- какая-то реализация интерфейсаIVideoChunk, которая, к тому же, использует реализациюFlatChunkдля работы с данными в виде сплошной последовательности байтов.
В этом коде есть несколько вещей (запахов), которые меня смущают, поэтому при реализации проекта, в котором возникла аналогичная ситуация, я использовал композицию/агрегацию, вместо наследования.
Например, так:
class IAudioChunk
{
public:
virtual IChunk* GetChunk() = 0;
virtual AudioFormat GetFormat() const = 0;
};
Но я бы хотел обсудить изначальную задумку.
Итак, проблемы первоначального кода заключаются в следующем:
1) SomeAudioChunk и SomeVideoChunk наследуют интерфейс IChunk дважды.
- Во-первых, мне не ясно: можно ли наследоваться от интерфейса и от реализации интерфейса без использования виртуального наследования?
- Во-вторых: потенциальные мысли о необходимости использования в коде виртуального наследования - это запах и повод серьезно задуматься над архитектурой;
- В-третьих: имеет ли значение, в каком порядке происходит наследование от интерфейса и от его реализации?
2) Я не уверен, что между типами IChunk и IAudioChunk, а также между типами IChunk и IVideoChunk, - существует отношение is a (является). На мой взгляд, has a в данной ситуации более уместно.
Доводы следующие:
- Если
IAudioChunkиIVideoChunkнаследовать отIChunk(отношениеis a), тогда при реализацииSomeAudioChunkиSomeVideoChunkнужно как-то и откуда-то брать реализацию интерфейсаIChunk; брать ее наследованием отFlatChunk- сомнительный процесс, а писать в ручную - глупо; - Если для
IAudioChunkиIVideoChunkиспользовать композицию/агрегацию, тогда все наследники данных классов не будут зависеть от реализацииFlatChunk, и даже смогут менять эту реализацию в процессе выполнения, и это очень даже хорошо, гибко, понятно и удобно.
Я хочу найти ответы на свои вопросы. В книгах найти их мне не удалось.
Ответы (1 шт):
Сначала напишу как я сделал бы:
class IChunk
{
public:
virtual void Method() = 0;
};
class FlatChunk : public IChunk
{
public:
void Method() override;
};
class IAudioChunk : virtual public FlatChunk
{
public:
virtual AudioFormat GetFormat() const = 0;
};
class IVideoChunk : virtual public FlatChunk
{
public:
virtual VideoFormat GetFormat() const = 0;
};
class SomeAudioChunk : public IAudioChunk
{
public:
AudioFormat GetFormat() const override;
};
class SomeVideoChunk : public IVideoChunk
{
public:
VideoFormat GetFormat() const override;
};
Потому что class FlatChunk уже содержит в себе весь интерфейс IChunk и определение всех или некоторых функций. Т.е. FlatChunk становится узловым классом для остальной иерархии, и он может быть или не быть абстрактным классом(это вам решать). Но как конкретно подойти к вопросу(использование, агрегация или наследование(открытое или закрытое), обо всем этом нужно еще подумать и пообсуждать)
В вашей реализации, например SomeAudioChunk, имеет все что есть в IAudioChunk и FlatChunk, а значит имеет все виртуальные методы и замещанные методы обеих классов. Т.е. он имеет два чистых виртуальных функции и одну определенную FlatChunk::Method. Т.е. ваши целевые классы в вашей реализации являются узловыми, а не целевыми. Теперь ответы на ваши вопросы, думаю проясняются, но, если нужно, отвечу конкретно на конкретные вопросы.
Да, наследование происходит дважды(это никчему)
- Да, можно наследовать и интерфейсный класс и класс реализации(но не знаю когда это может понадобится)
Над архитектурой по любому нужно серьезно задуматься. Виртуальное
наследование всего лишь инструмент наследовать от одного и того же обьекта, оно ничего не усложняет.
Нет
- Если наследование открытое, то существует отношение
is a