策略模式 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。要解决此问题,您可以执行以下操作:

  1. 在不继承QObject的情况下使ViewInterface抽象化。这个抽象class 将定义您的 FancyView 和 TextView 的接口,并且可能会或可能不会实现一些通用逻辑。

  2. 通过分别继承自 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*。但这意味着您需要更改相当多的代码,而且在您的情况下实际上可能不是那么好。

针对给定的评论,我提出另一个解决方案:

  1. QWidget继承ViewInterface(具有所有需要的接口函数):

    class ViewInterface: public QWidget {
        Q_OBJECT
        ...
    }
    
  2. 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);
    }
    
  3. 在派生的构造函数中 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_;
    }
    
  4. 使用目标小部件实现所需的界面。如果你想处理事件,你可以使用QObject::eventFilter ()。在这种情况下,您应该将上面代码中的 FancyView 对象设置为 dialog_widget_.

  5. 的事件过滤器

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

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

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

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

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

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

如果你想在 ViewInterface 的两个具体实现之间共享一些代码,你可以从 ViewInterface 派生一个额外的 class 来提供共享代码。 TerminalViewFancyView 都将派生自 class。助手 class 可以在基础 class 类型上进行参数化,让它跳过更少的环节来访问小部件,例如:class TerminalView : ViewHelper<QPlainTextEdit> { ... };