C ++: реализация нескольких экземпляров интерфейса или дополнительного интерфейса в классе - PullRequest
0 голосов
/ 28 марта 2020

У меня возникают проблемы с поиском информации о передовой практике, которая, как мне кажется, должна быть довольно распространенной проблемой.

Я начну с конкретного примера c (связанного с обновлением программного обеспечения), поскольку он делает обсуждение более конкретное, но проблема должна быть довольно общей c.

Скажите, что у меня есть интерфейс программного обновления:

class Software_updater {
    virtual ~Software_updater() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

Для моей первой реализации A, мне повезло все просто.

class A_software_updater : public Software_updater {
    // ...
};

A B_software_updater, однако, сложнее. Как и в случае A, он подключается к цели для нетривиального обновления и поддерживает состояние целевого соединения. Но что еще более важно, он может обновить два образа: образ приложения и образ загрузчика.

Как и у меня, я не вижу реальной причины для go для рефакторинга, поэтому я предполагаю, что я может просто опираться на это. Я пришел к следующему решению:

class B_software_updater {
public:
    Software_updater& application_updater() { return application_updater_; }
    Software_updater& boot_loader_updater() { return boot_loader_updater_; }
private:
    class Application_updater : public Software_updater {
        // ...
    } application_updater_;
    class Boot_loader_updater : public Software_updater {
        // ...
    } boot_loader_updater_;
};

Т.е. я возвращаю не-const ссылки на "интерфейсы" для переменных-членов. Обратите внимание, что они не могут быть const, поскольку они отключены.

Запрос 1: Я думаю, что приведенное выше решение является чистым, но я был бы рад получить некоторое подтверждение .

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

class Optional_interface {
    virtual ~Optional_interface() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

class A_implementation {
public:
#ifdef OPTIONAL_FEATURE
    Optional_interface& optional_interface() { return optional_implementation_; }
#endif
    // ...
private:
#ifdef OPTIONAL_FEATURE
    class Optional_implementation : public Optional_interface {
        // ...
    } optional_implementation_;
#endif
    // ...
};

Запрос 2: я не смог найти простой (как в: не слишком сложный на основе шаблонов) и чистый способ express необязательного наследования во время компиляции на уровне A_implementation. Вы можете?

1 Ответ

0 голосов
/ 28 марта 2020

Лучшее решение

Основываясь на комментарии @ ALX23z о недействительности ссылки на переменную-член при перемещении, я сейчас отклоняю свое первоначальное решение (исходное сообщение). Эта проблема недействительности не была бы проблемой для моего случая, но я нахожусь в поиске универсального c шаблона.

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

Сначала краткий обзор моей первоначальной проблемы.

Скажите, что у меня есть интерфейс программы обновления программного обеспечения (или любой интерфейс, это только пример):

class Software_updater {
    virtual ~Software_updater() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

A B_software_updater может обновить два images: образ приложения и образ загрузчика. Поэтому он хочет предоставить два экземпляра интерфейса Software_updater.

Решение, которое лучше, чем в моем исходном посте, состоит в объявлении B_application_updater и B_boot_loader_updater, созданных из B_software_updater&, вне B_software_updater, и реализуется клиентским кодом.

class B_application_updater : public Software_updater {
    B_application_updater(B_software_updater&);
    // ...
};
class B_boot_loader_updater : public Software_updater {
    B_application_updater(B_boot_loader_updater&);
    // ...
};

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

Это будет работать и для дополнительного интерфейса (см. исходное сообщение):

class A_optional_implementation : public Optional_interface {
    A_optional_implementation(A_implementation&);
};

A_optional_implementation будет объявлено за пределами A_implementation.

Приложения, которые не нуждаются в этом интерфейсе, просто не будут создавать экземпляры A_optional_implementation.

Дополнительные мысли

Это приложение шаблона проектирования адаптера!

По сути, что это за ответ сводится к:

  • класс Interface.
  • класс Implementation, который выполняет работу, но на самом деле не заботится об интерфейсе. Он не наследует Interface. Дело в том, что Implementation может «выполнять работу», соответствующую нескольким интерфейсам, без сложности и недостатков множественного наследования (конфликты имен и т. Д. c.). Он также может выполнять работу, соответствующую нескольким экземплярам одного и того же интерфейса (мой случай выше).
  • Класс Interface_adapter, который принимает параметр Implementation& в своем конструкторе. Он наследует Interface, то есть эффективно реализует его, и это его единственная цель.

Делая шаг назад, я понимаю, что это просто приложение шаблона адаптера (хотя Implementation в этом случае не обязательно реализовывать какой-либо внешний интерфейс - его интерфейс - это просто его публичные c функции-члены)!

Промежуточное решение: оставить классы адаптера внутри реализации class

В приведенном выше решении я указываю, что классы адаптера объявляются вне классов реализации. Хотя это кажется логичным для случая с традиционным шаблоном адаптера, для моего случая я мог бы также объявить их внутри класса реализации (как я это делал в исходном посте) и сделать их опубликованными c. Клиентскому коду все равно придется создавать объекты реализации и адаптера, но классы адаптера будут принадлежать пространству имен реализации, которое выглядит лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...