【发布时间】:2020-06-30 08:08:44
【问题描述】:
我的任务是创建一种多平台 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 idiom 模式的细节才能正确初始化实现。
- 简单的实现指针 - 用户不需要知道库的底层设计 - 如果他想创建自定义控件,他只需将库控件子类化,其余的由库处理
#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 类和一大堆其他类支持的所有方法都需要一个访问方法。
有人可以告诉我,如何修改这些解决方案,或者提出其他解决方案,这在这种情况下会更有意义吗?提前致谢。
EDIT 基于 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
问题在于在Button 的ctor 中调用Base(bi) 将不起作用,因为buttonimpl 不继承baseimpl,只是它的子类FrameWorkABase。
【问题讨论】:
-
您不能在同一个程序中有两个不同的
Base::impl定义(或者,在您的第二种方法中,impl)。根据您的操作方式,结果要么是可诊断的错误(如果两个定义在同一个编译单元中可见)或未定义的行为(如果定义在不同的编译单元中)。 -
您似乎正在使用继承来处理它并不真正需要的事情。例如,为什么需要
FrameWorkABase基类? -
@ruakh 表示 GUI 控制的基础对象 - 例如在 Qt 中它是 QWidget。然后,您可以定义所有控件通用的方法,如 show() 或 getPosition()。
-
看Base中的这段代码:impl* pimpl_;在您的代码知道 FrameWorkAButton 的地方,为什么不使用它来代替:FrameWorkAButton* pimpl_; ?
-
@ruakh 根据您的回答,FrameWorkABase 是一个通用的 GUI 控件对象。如果是这种情况,我猜每个控件对象都是可点击的,这意味着 FrameWorkABase 类应该有一个虚拟的
click()函数,并且每个子类都应该实现自己的点击方法。这样您就可以使用多态性来调用特定行为而无需向下转换(在这种情况下,这是一个糟糕的设计,因为它会产生依赖关系)
标签: c++ oop design-patterns polymorphism pimpl-idiom