Множественное наследование интерфейса

Рассмотрим следующий код:

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 шт):

Автор решения: AR Hovsepyan

Сначала напишу как я сделал бы:

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. Т.е. ваши целевые классы в вашей реализации являются узловыми, а не целевыми. Теперь ответы на ваши вопросы, думаю проясняются, но, если нужно, отвечу конкретно на конкретные вопросы.

  1. Да, наследование происходит дважды(это никчему)

    • Да, можно наследовать и интерфейсный класс и класс реализации(но не знаю когда это может понадобится)
    • Над архитектурой по любому нужно серьезно задуматься. Виртуальное

    • наследование всего лишь инструмент наследовать от одного и того же обьекта, оно ничего не усложняет.

    • Нет

  2. Если наследование открытое, то существует отношение is a
→ Ссылка