У меня есть задание создать своего рода мультиплатформенную библиотеку C++ GUI. Он охватывает разные графические интерфейсы на разных платформах. Сама библиотека предоставляет интерфейс, через который пользователь общается единообразно, независимо от используемой им платформы.
Мне нужно правильно спроектировать этот интерфейс и базовую связь с фреймворком. Что я пробовал:
- Идиома Pimpl - это решение было выбрано сначала из-за его преимуществ - бинарная совместимость, вырезание дерева зависимостей для увеличения времени сборки...
class Base {
public:
virtual void show();
// other common methods
private:
class impl;
impl* pimpl_;
};
#ifdef Framework_A
class Base::impl : public FrameWorkABase{ /* underlying platform A code */ };
#elif Framework_B
class Base::impl : public FrameWorkBBase { /* underlying platform B code */ };
#endif
class Button : public Base {
public:
void click();
private:
class impl;
impl* pimpl_;
};
#ifdef Framework_A
class Button::impl : public FrameWorkAButton{ /* underlying platform A code */ };
#elif Framework_B
class Button::impl : public FrameWorkBButton { /* underlying platform B code */ };
#endif
Однако, насколько я понимаю, этот шаблон не был разработан для такой сложной иерархии, где вы можете легко расширить как интерфейсный объект, так и его реализацию. Например. если пользователь хочет создать подкласс кнопки из библиотеки UserButton : Button, ему нужно знать особенности шаблона идиомы pimpl, чтобы правильно инициализировать реализацию.
- Простой указатель реализации - пользователю не нужно знать базовый дизайн библиотеки - если он хочет создать собственный элемент управления, он просто создает подклассы элемента управления библиотеки, а обо всем остальном позаботится библиотека.
#ifdef Framework_A
using implptr = FrameWorkABase;
#elif Framework_B
using implptr = FrameWorkBBase;
#endif
class Base {
public:
void show();
protected:
implptr* pimpl_;
};
class Button : public Base {
public:
void click() {
#ifdef Framework_A
pimpl_->clickA(); // not working, need to downcast
#elif Framework_B
// works, but it's a sign of a bad design
(static_cast<FrameWorkBButton>(pimpl_))->clickB();
#endif
}
};
Поскольку реализация защищена, один и тот же объект implptr будет использоваться в Button — это возможно, потому что и FrameWorkAButton, и FrameWorkBButton наследуются от FrameWorkABBase и FrameWorkABase соответственно. Проблема с этим решением заключается в том, что каждый раз, когда мне нужно позвонить, например. в классе Button что-то вроде pimpl_->click(), мне нужно преобразовать pimpl_, потому что метод clickA() находится не в FrameWorkABase, а в FrameWorkAButton, поэтому он будет выглядеть так (static_cast<FrameWorkAButton>(pimpl_))->click(). А чрезмерное занижение — признак плохого дизайна. Шаблон посетителя в этом случае неприемлем, так как должен быть метод посещения для всех методов, поддерживаемых классом Button и целой кучей других классов.
Может ли кто-нибудь сказать мне, как изменить эти решения или, может быть, предложить какие-то другие, которые будут иметь больше смысла в этом контексте? Заранее спасибо.
EDIT на основе ответа @ruakh
Таким образом, решение pimpl будет выглядеть так:
class baseimpl; // forward declaration (can create this in some factory)
class Base {
public:
Base(baseimpl* bi) : pimpl_ { bi } {}
virtual void show();
// other common methods
private:
baseimpl* pimpl_;
};
#ifdef Framework_A
class baseimpl : public FrameWorkABase{ /* underlying platform A code */ };
#elif Framework_B
class baseimpl : public FrameWorkBBase { /* underlying platform B code */ };
#endif
class buttonimpl; // forward declaration (can create this in some factory)
class Button : public Base {
public:
Button(buttonimpl* bi) : Base(bi), // this won't work
pimpl_ { bi } {}
void click();
private:
buttonimpl* pimpl_;
};
#ifdef Framework_A
class Button::impl : public FrameWorkAButton{ /* underlying platform A code */ };
#elif Framework_B
class Button::impl : public FrameWorkBButton { /* underlying platform B code */ };
#endif
Проблема в том, что вызов Base(bi) внутри ctor Button не сработает, так как buttonimpl не наследует baseimpl, а только его подкласс FrameWorkABase.
Base::impl(или, во втором подходе,impl) в одной и той же программе. В зависимости от того, как вы это сделаете, результатом будет либо диагностируемая ошибка (если оба определения видны в одной и той же единице компиляции), либо неопределенное поведение (если определения находятся в разных единицах компиляции). - person Peter   schedule 19.03.2020FrameWorkABase? - person ruakh   schedule 19.03.2020click(), и каждый подкласс должен реализовывать свой собственный метод щелчка. Таким образом, вы используете полиморфизм для вызова определенного поведения без понижения (что в данном случае является плохим дизайном, поскольку создает зависимости). - person SubMachine   schedule 19.03.2020click()должен был быть методом, специфичным только для кнопок, но я думаю, что это был плохой пример. Как насчетsetValue()для счетчика илиaddItem()для списка? Они не могут быть в классеBase. - person mrdecompilator   schedule 19.03.2020FrameWorkBButton(например) классом, который вы создаете, чтобы он был прокладкой между вашим классомButtonи функциональностью кнопки базовой платформы? Или это класс в базовой структуре? Я спрашиваю, потому что в первом случае я не понимаю, почемуFrameWorkAButtonиFrameWorkBButtonпредоставляют разные интерфейсы, а во втором случае я не думаю, что вы можете предположить, что каждая нижележащая структура имеет одинаковую иерархию классов (или даже имеет какую-либо иерархия классов — что, если она написана для совместимости с C)? - person ruakh   schedule 20.03.2020FrameWorkAButtonпредставляет кнопку пользовательского интерфейса фреймворка, которая отображается в пользовательском интерфейсе. Вы можете представитьFramework_Aкак Qt,FrameWorkABaseкакQWidgetиFrameWorkAButtonкакQPushButton. В QtQPushButtonнаследуется отQWidget, поэтому я могу использовать этот тип полиморфизма. и этот тип иерархии применим к каждой структуре, которую я собираюсь использовать. Мои классыBaseилиButtonпросто представляют собой интерфейс, через который я могу взаимодействовать с каждым из элементов управления фреймворка. - person mrdecompilator   schedule 21.03.2020