【问题标题】:Strategy pattern Qt ambiguous base策略模式 Qt 模糊基
【发布时间】:2017-12-09 14:54:02
【问题描述】:

对于一个学校项目,我们必须制作一个“游戏”,在这个游戏中,我们想象一个迷宫并找到我们的出路。这种可视化必须以两种不同的方式实现,在我们的例子中是基于文本的可视化和更奇特的方式。

为此我们必须使用 Qt,我们正在使用一个 QMainWindow,其中有一个 QWidget,这个小部件将是一个或另一个可视化。看到在游戏过程中我们应该可以在可视化之间切换,我们使用了策略模式,所以做了一个界面(ViewInterface),两个可视化都实现了这个。除了实现 ViewInterface 之外,两个可视化都继承了另一个类,在基于文本的情况下这是 QPlainTextEdit(有一个带有文本的 QWidget),而在 Fancy 中这是 QDialog。这里的问题是,在我的控制器中,我有一个指向 ViewInterface 的指针,该指针用于填充 QWidet 但要执行此 ViewInterface 还必须从 QWidget 继承,这会导致此错误:QObject is an ambiguous base of 'TerminalView'。

由于在玩游戏时可以在视图之间切换,并且只能在当前活动的视图上调用更新,因此我们不能将特定视图传递给“setWidget”。

我做错了什么或者我该如何解决这个问题? (我已经考虑过了,但无法提供解决方案)。

【问题讨论】:

    标签: c++ qt strategy-pattern ambiguous


    【解决方案1】:

    这里的问题是你继承了 QObject 两次:第一次在 ViewInterface 层次结构中,第二次在 QDialog 层次结构中 (diamond problem)。 尝试为您的 FancyView 和 TextView 类使用虚拟继承不起作用,因为应该在整个层次结构中使用虚拟继承

    但是您的设计还有另一个问题:QDialog 和 QPlainTextEdit 都继承了 QWidget。要解决此问题,您可以执行以下操作:

    1. 使 ViewInterface 抽象而不从 QObject 继承。这个抽象类将定义你的 FancyView 和 TextView 的接口,可能会也可能不会实现一些常见的逻辑。

    2. 分别从 QDialog 和 ViewInterface 以及 QPlainTextEdit 和 ViewInterface 实现 FancyView 和 TextView。

    这样你可能不会遇到任何模糊基类的问题。

    更新:

    还没有看到您的编辑:这确实可以解决问题,但是 另一个问题出现了:如果我这样做,我就不能使用 ViewInterface 设置我的 QWidget 的指针。确实有可能,但在我看来 这不是很干净

    嗯,这是一个真正的问题。一个明显的解决方案是不要使用ViewInterface* 而不是QWidget*。但这意味着您需要更改大量代码,而且在您的情况下实际上可能不是那么好。

    关于给定的评论,我提出另一种解决方案:

    1. QWidget 继承ViewInterface(具有所有所需的接口功能):

      class ViewInterface: public QWidget {
          Q_OBJECT
          ...
      }
      
    2. ViewInterface构造函数中设置小部件使用的布局并设置它:

      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);
      }
      
    3. 在派生类的构造函数中,将特定的小部件添加到布局中:

      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_;
      }
      
    4. 使用目标小部件实现所需的界面。如果你想处理事件,你可以使用QObject::eventFilter ()。在这种情况下,您应该将上面代码中的FancyView 对象设置为dialog_widget_ 的事件过滤器。

    注意:在这种情况下,您不能将FancyView 用作QDialog。这个问题的一个工作量是代理QDialog 信号、槽和公共函数,并创建另一个包装类FancyViewDialog,作为FancyView 中方法、信号和槽的代理。这不是一个很好的解决方案,但我没有看到任何其他解决钻石问题的方法,它允许ViewInterfaceQWidget 之间存在“is-a”关系。

    【讨论】:

    • 如果我这样做,我得到错误 'QObject' is an ambiguous base of 'TerminalView' 我的代码:class TerminalView : public virtual QPlainTextEdit, public virtual ViewInterface { Q_OBJECT ... };
    • 还没有看到您的编辑:确实这会解决问题,但会出现另一个问题:如果我这样做,我不能使用 ViewInterface 指针来设置我的 QWidget。确实有可能,但在我看来这不是很干净
    【解决方案2】:

    接口应该是带有虚拟方法的抽象接口,并且没有具体的基类。 ViewInterface 不应继承自 QWidget。这解决了你的问题。

    现在至少有两种解决方案可以将ViewInterface 实例转换为QWidget

    1. 模板ViewInterface 的用户并让他们确保所使用的具体类型确实派生自QWidget。如果类型没有运行时多态性,这将起作用。

    2. 如果存在运行时多态,在接口中添加QWidget * widget() = 0方法,并在派生方法中实现。这很简单:QWidget * widget() override { return this; }

    接口可以同时具有信号和槽——它们必须是虚拟方法,但它们肯定会起作用。参见例如this answer 开始使用虚拟信号。

    如果您想在ViewInterface 的两个具体实现之间共享一些代码,您可以拥有一个派生自ViewInterface 的附加类来提供共享代码。 TerminalViewFancyView 都将派生自该类。辅助类可以在基类类型上进行参数化,使其跳过更少的循环来访问小部件,例如:class TerminalView : ViewHelper<QPlainTextEdit> { ... };

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-03
      • 2016-12-13
      相关资源
      最近更新 更多