Где реализовать функции из родительского интерфейса интерфейса? - PullRequest
0 голосов
/ 04 октября 2019

Меня просят реализовать интерфейс, и мне интересно, какова была бы лучшая стратегия для максимально возможной факторизации кода.

Вот определение интерфейса (я не должен менятьэто):

#include <string>

class BaseIf
{
public:
    virtual ~BaseIf() {}

    virtual std::string getName() = 0;
};

class IntIf : public BaseIf
{
public:
    virtual ~IntIf() {}

    virtual int getValue() = 0;
};

class FloatIf : public BaseIf
{
public:
    virtual ~FloatIf() {}

    virtual float getValue() = 0;
};

В итоге я получу IntImpl (реализация IntIf) и FloatImpl (реализация FloatIf). Но мне интересно, куда я должен поместить любой код, общий для этих двух классов (например, управление атрибутами name или любой другой материал, необходимый для BaseIf, который на самом деле намного больше, чем в этом MCVE).

Если я создаю BaseImpl (реализующую BaseIf функцию getName) с помощью общего кода и получаю из него IntImplIntIf), то мне нужно также реализовать в нем getName, посколькусообщается, что не реализовано. И я также получаю двойное наследование BaseIf ...

Мне было интересно, поможет ли шаблон Pimpl, тогда IntImpl будет иметь объект BaseImpl в качестве атрибута (и получен только из IntIf), но затем, опять же, мне нужно реализовать getName в IntImpl, чтобы «переслать» вызов атрибуту BaseImpl. Так как BaseIf на самом деле имеет много виртуальных функций, поддерживать его будет просто сложно.

Нет ли разумного решения / шаблона, позволяющего реализовать только один раз getName в обычном месте? Или это просто интерфейс, который плох и должен быть переработан?

Ответы [ 3 ]

3 голосов
/ 04 октября 2019

Это основной вариант использования для виртуального наследования.

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

class BaseIf
{
public:
    virtual ~BaseIf() {}

    virtual std::string getName() = 0;
};

class IntIf : public virtual BaseIf
{
public:
    virtual ~IntIf() {}

    virtual int getValue() = 0;
};

class BaseImpl : public virtual BaseIf
{
  public:
    std::string getName () override { return "whoa dude"; }
};

class IntImpl : public virtual IntIf, public BaseImpl
{
  public:
    int getValue() override { return 42; }
};

full demo

При более глубокой иерархии, вероятно, придется виртуально наследовать и классы реализации, что не очень удобно, но все жевыполнимо.

Альтернативой виртуальному наследованию реализации было бы разделение реализации на слой «строительных блоков» и конечный уровень. Строительные блоки автономны и не наследуют другие строительные блоки. (Они могут наследовать интерфейсы). Конечные классы наследуют строительные блоки, но не другие конечные классы.

class BaseBlock : public virtual BaseIf
{
  public:
    std::string getName () override { return "whoa dude"; }
};

class IntBlock : public virtual IntIf
{
  public:
    int getValue() override { return 42; }
};

class BaseImpl : public BaseBlock {};

class IntImpl : public BaseBlock, public IntBlock {};

full demo

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

Без виртуального наследования придется прибегнуть к множеству шаблонов.

class BaseBlock // no base class!
{
  public:
    virtual std::string getName () { return "whoa dude"; }
};

class BaseImpl : public BaseIf, public BaseBlock
{
  public:
    // oops, getName would be ambiguous here, need boplerplate
    std::string getName () override { return BaseBlock::getName(); }
};
1 голос
/ 04 октября 2019

Вы можете создать шаблонный класс, который реализует общую часть интерфейса следующим образом:

template <class IFACE> class BaseImpl : public IFACE
{
    public:
    std::string getName () override { ... }
}

, а затем

class IntImpl : public BaseImpl<IntIf>
{
    public:
    int getValue() override { ... }
}

В результате получается простая цепочка с одним наследованием. BaseIf <- <code>IntIf <- <code>BaseImpl <- <code>IntImpl

Убедитесь, что у вас есть веские основания для существования IntIf и FloatIf, хотя - в вашем MCVE они выглядяткак будто они вообще не должны быть там.

0 голосов
/ 04 октября 2019

Вы можете предоставить реализацию по умолчанию для чисто виртуальных функций:

struct A {
    virtual void frob() = 0;
};

void A::frob() {
    std::cout << "default";
}

struct B : A {
    void frob() override {
        A::frob(); // calls the default
    }
};

Если я правильно читаю вашу проблему, вам нужна реализация по умолчанию для getName(). Так что решите это, просто предоставьте реализацию и назовите ее:

class IntIf : public BaseIf
{
public:
    virtual ~IntIf() {}
    virtual int getValue() = 0;

    std::string getName() override {
        return BaseIf::getName();
    }
};

class FloatIf : public BaseIf
{
public:
    virtual ~FloatIf() {}
    virtual float getValue() = 0;

    std::string getName() override {
        return BaseIf::getName();
    }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...