策略模式 Qt 模糊基础
Strategy pattern Qt ambiguous base
对于一个学校项目,我们必须制作一个 'game',在这个游戏中,我们想象一个迷宫并找到穿过它的路。这种可视化必须以两种不同的方式实现,在我们的例子中是基于文本的可视化和更奇特的方式。
为此我们必须使用 Qt,我们正在使用带有 QWidget 的 QMainWindow,这个小部件将是一个或另一个可视化。看到在游戏中我们应该能够在可视化之间切换,我们使用策略模式,所以做了一个界面(ViewInterface)并且两个可视化都实现了这个。除了实现 ViewInterface 之外,这两种可视化都继承了另一个 class,在基于文本的情况下,这是 QPlainTextEdit(具有带文本的 QWidget),在 Fancy 中,这是 QDialog。这里的问题是,在我的控制器中,我有一个指向用于填充 QWidet 的 ViewInterface 的指针,但是要执行此 ViewInterface 也必须继承自 QWidget,这会导致此错误:QObject is an ambiguous base of 'TerminalView' .
由于可以在玩游戏的同时切换视图,并且只应在当前活动视图上调用更新,因此我们无法将特定视图传递给'setWidget'。
我是不是做错了什么或者我该如何解决这个问题? (我已经考虑过了,但无法提供解决方案)。
这里的问题是您继承了 QObject 两次:第一次是在 ViewInterface 层次结构中,第二次是在 QDialog 层次结构中 (diamond problem)。 尝试为您的 FancyView 和 TextView 使用虚拟继承 classes (不起作用,因为虚拟继承应该在整个层次结构中使用)
但是你的设计还有一个问题:QDialog和QPlainTextEdit都继承了QWidget。要解决此问题,您可以执行以下操作:
在不继承QObject的情况下使ViewInterface抽象化。这个抽象class 将定义您的 FancyView 和 TextView 的接口,并且可能会或可能不会实现一些通用逻辑。
通过分别继承自 QDialog 和 ViewInterface 以及 QPlainTextEdit 和 ViewInterface 的多重继承来实现 FancyView 和 TextView。
这样你就不会遇到任何基数不明确的问题class。
更新:
Did not see your edit yet: indeed this would solve the issue, but
another problem rises: if I do this, I can't use a ViewInterface
pointer to set my QWidget. It is possible indeed, but in my opinion
this is not really clean
嗯,这是一个真正的问题。一个明显的解决方案是不使用 ViewInterface*
而不是 QWidget*
。但这意味着您需要更改相当多的代码,而且在您的情况下实际上可能不是那么好。
针对给定的评论,我提出另一个解决方案:
从QWidget
继承ViewInterface
(具有所有需要的接口函数):
class ViewInterface: public QWidget {
Q_OBJECT
...
}
在ViewInterface
构造函数中设置widget要使用的layout并设置:
ViewInterface::ViewInterface (QWidget* i_parent)
: QWidget (i_parent) {
auto layout {new QGridLayout (this)};
// Zeroed margins to make full fit.
layout->setContentsMargins (0, 0, 0, 0);
setLayout (layout);
}
在派生的构造函数中 classes 添加特定的小部件到布局:
class FancyView : public ViewInterface {
Q_OBJECT
FancyView (QWidget* i_parent)
: ViewInterface (i_parent)
, dialog_widget_ {new QDialog (this)} {
layout->addWidget (dialog_widget_);
}
...
private:
QDialog* dialog_widget_;
}
使用目标小部件实现所需的界面。如果你想处理事件,你可以使用QObject::eventFilter ()。在这种情况下,您应该将上面代码中的 FancyView
对象设置为 dialog_widget_
.
的事件过滤器
注意:在这种情况下,您不能将 FancyView
用作 QDialog
。这个问题的解决方法是代理 QDialog
信号、槽和 public 函数,并创建另一个包装器 class FancyViewDialog
,作为方法、信号和槽的代理FancyView
。这不是一个很好的解决方案,但我没有看到任何其他解决钻石问题的方法,允许 ViewInterface
和 QWidget
之间的 "is-a" 关系。
接口应该是具有虚方法的抽象接口,并且没有具体的基 classes。 ViewInterface
不应继承自 QWidget
。这解决了你的问题。
现在至少有两种解决方案可以将 ViewInterface
实例转换为 QWidget
:
为 ViewInterface
的用户创建模板,并让他们确保所使用的具体类型确实派生自 QWidget
。如果类型没有运行时多态性,那将起作用。
如果存在运行时多态,在接口中添加一个QWidget * widget() = 0
方法,在派生方法中实现。这很简单:QWidget * widget() override { return this; }
.
接口可以同时具有信号和槽 - 它们必须是虚拟方法,但它们肯定会起作用。参见例如this answer 开始使用虚拟信号。
如果你想在 ViewInterface
的两个具体实现之间共享一些代码,你可以从 ViewInterface
派生一个额外的 class 来提供共享代码。 TerminalView
和 FancyView
都将派生自 class。助手 class 可以在基础 class 类型上进行参数化,让它跳过更少的环节来访问小部件,例如:class TerminalView : ViewHelper<QPlainTextEdit> { ... };
对于一个学校项目,我们必须制作一个 'game',在这个游戏中,我们想象一个迷宫并找到穿过它的路。这种可视化必须以两种不同的方式实现,在我们的例子中是基于文本的可视化和更奇特的方式。
为此我们必须使用 Qt,我们正在使用带有 QWidget 的 QMainWindow,这个小部件将是一个或另一个可视化。看到在游戏中我们应该能够在可视化之间切换,我们使用策略模式,所以做了一个界面(ViewInterface)并且两个可视化都实现了这个。除了实现 ViewInterface 之外,这两种可视化都继承了另一个 class,在基于文本的情况下,这是 QPlainTextEdit(具有带文本的 QWidget),在 Fancy 中,这是 QDialog。这里的问题是,在我的控制器中,我有一个指向用于填充 QWidet 的 ViewInterface 的指针,但是要执行此 ViewInterface 也必须继承自 QWidget,这会导致此错误:QObject is an ambiguous base of 'TerminalView' .
由于可以在玩游戏的同时切换视图,并且只应在当前活动视图上调用更新,因此我们无法将特定视图传递给'setWidget'。
我是不是做错了什么或者我该如何解决这个问题? (我已经考虑过了,但无法提供解决方案)。
这里的问题是您继承了 QObject 两次:第一次是在 ViewInterface 层次结构中,第二次是在 QDialog 层次结构中 (diamond problem)。 尝试为您的 FancyView 和 TextView 使用虚拟继承 classes (不起作用,因为虚拟继承应该在整个层次结构中使用)
但是你的设计还有一个问题:QDialog和QPlainTextEdit都继承了QWidget。要解决此问题,您可以执行以下操作:
在不继承QObject的情况下使ViewInterface抽象化。这个抽象class 将定义您的 FancyView 和 TextView 的接口,并且可能会或可能不会实现一些通用逻辑。
通过分别继承自 QDialog 和 ViewInterface 以及 QPlainTextEdit 和 ViewInterface 的多重继承来实现 FancyView 和 TextView。
这样你就不会遇到任何基数不明确的问题class。
更新:
Did not see your edit yet: indeed this would solve the issue, but another problem rises: if I do this, I can't use a ViewInterface pointer to set my QWidget. It is possible indeed, but in my opinion this is not really clean
嗯,这是一个真正的问题。一个明显的解决方案是不使用 ViewInterface*
而不是 QWidget*
。但这意味着您需要更改相当多的代码,而且在您的情况下实际上可能不是那么好。
针对给定的评论,我提出另一个解决方案:
从
QWidget
继承ViewInterface
(具有所有需要的接口函数):class ViewInterface: public QWidget { Q_OBJECT ... }
在
ViewInterface
构造函数中设置widget要使用的layout并设置:ViewInterface::ViewInterface (QWidget* i_parent) : QWidget (i_parent) { auto layout {new QGridLayout (this)}; // Zeroed margins to make full fit. layout->setContentsMargins (0, 0, 0, 0); setLayout (layout); }
在派生的构造函数中 classes 添加特定的小部件到布局:
class FancyView : public ViewInterface { Q_OBJECT FancyView (QWidget* i_parent) : ViewInterface (i_parent) , dialog_widget_ {new QDialog (this)} { layout->addWidget (dialog_widget_); } ... private: QDialog* dialog_widget_; }
使用目标小部件实现所需的界面。如果你想处理事件,你可以使用QObject::eventFilter ()。在这种情况下,您应该将上面代码中的
FancyView
对象设置为dialog_widget_
. 的事件过滤器
注意:在这种情况下,您不能将 FancyView
用作 QDialog
。这个问题的解决方法是代理 QDialog
信号、槽和 public 函数,并创建另一个包装器 class FancyViewDialog
,作为方法、信号和槽的代理FancyView
。这不是一个很好的解决方案,但我没有看到任何其他解决钻石问题的方法,允许 ViewInterface
和 QWidget
之间的 "is-a" 关系。
接口应该是具有虚方法的抽象接口,并且没有具体的基 classes。 ViewInterface
不应继承自 QWidget
。这解决了你的问题。
现在至少有两种解决方案可以将 ViewInterface
实例转换为 QWidget
:
为
ViewInterface
的用户创建模板,并让他们确保所使用的具体类型确实派生自QWidget
。如果类型没有运行时多态性,那将起作用。如果存在运行时多态,在接口中添加一个
QWidget * widget() = 0
方法,在派生方法中实现。这很简单:QWidget * widget() override { return this; }
.
接口可以同时具有信号和槽 - 它们必须是虚拟方法,但它们肯定会起作用。参见例如this answer 开始使用虚拟信号。
如果你想在 ViewInterface
的两个具体实现之间共享一些代码,你可以从 ViewInterface
派生一个额外的 class 来提供共享代码。 TerminalView
和 FancyView
都将派生自 class。助手 class 可以在基础 class 类型上进行参数化,让它跳过更少的环节来访问小部件,例如:class TerminalView : ViewHelper<QPlainTextEdit> { ... };