У меня есть задание на создание своего рода мультиплатформенной библиотеки C ++ GUI. Он охватывает различные фреймворки GUI на разных платформах. Сама библиотека предоставляет интерфейс, через который пользователь общается равномерно, независимо от используемой платформы.
Мне нужно правильно спроектировать этот интерфейс и основную связь с платформой. Я попробовал следующее:
- Pimpl idiom - сначала это решение было выбрано из-за его преимуществ - двоичной совместимости, сокращения дерева зависимостей для увеличения времени сборки ...
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
}
};
Поскольку реализация защищена, в Button
будет использоваться один и тот же объект implptr
- это возможно, поскольку оба FrameWorkAButton
и FrameWorkBButton
наследуются от FrameWorkABBase
и FrameWorkABase
соответственно , Проблема с этим решением состоит в том, что каждый раз, когда мне нужно вызвать, например, в Button
классе что-то вроде pimpl_->click()
, мне нужно уменьшить pimpl_
, потому что clickA()
метод не в FrameWorkABase
, а в FrameWorkAButton
так это будет выглядеть так (static_cast<FrameWorkAButton>(pimpl_))->click()
. А чрезмерное удручение - признак плохого дизайна. Шаблон посетителя в этом случае неприемлем, так как для всех методов, поддерживаемых классом Button
и целой кучей других классов, должен быть метод посещения.
Может кто-нибудь сказать, как его изменить? эти решения или, может быть, предложить другие, которые имеют больше смысла в этом контексте? Заранее спасибо.
РЕДАКТИРОВАТЬ на основе ответа od @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
.