c++ - 扩展公共(public)基础 : Diamond inheritance vs. QObject

标签 c++ qt inheritance qwidget qt-signals

我想我在这里遇到了一种菱形继承(钻石问题)问题。

Qt 提供了几个旋转框,用于整数值、 double 值以及日期/时间。它们都派生自 QAbstractSpinBox:

#include <QtWidgets/QSpinBox>
class QSpinBox:
    public QAbstractSpinBox {

};

#include <QtWidgets/QDoubleSpinBox>
class QDoubleSpinBox:
    public QAbstractSpinBox {

};

现在我想添加一些所有旋转框通用的功能,在这个具体示例中,一个按钮将旋转框恢复到其最小值(因此是 specialValueText)。 所以我也派生自 QAbstractSpinBox 并想出了这样的东西:

class AbstractRevertibleSpinBox:
    public QAbstractSpinBox {

    public:
        RevertibleSpinBox() {
            /* Use abstract base: */
            QAction *revertAction = new QAction(this);
            QAbstractSpinBox::lineEdit()->addAction(
                revertAction, QLineEdit::TrailingAction);
            /* ... */
        }

    public slots:
        virtual void revert()  = 0;
}

这包含纯 revert() 应该实现如何恢复不同的旋转框。例如,为 QDoubleSpinBox 使用 setValue(double) 或为 QDateEdit 使用 setDate(QDate)。 然后我采用了显而易见的方式并为我需要的所有旋转框派生了适当的类,如下所示:

class RevertibleSpinBox:
    public QSpinBox,
    public AbstractRevertibleSpinBox {

    protected:
        void revert() {
            /* Revert 'int' */
            setValue(0);
        }
};

class RevertibleDoubleSpinBox:
    public QDoubleSpinBox,
    public AbstractRevertibleSpinBox {

    protected:
        void revert() {
            /* Revert 'double' */
            setValue(0.0);
        }
};

这显然不起作用,因为现在 QAbstractSpinBox 中的任何内容都是模棱两可的。我想我可以使用虚拟继承来解决它,如果,例如,这会起作用。 QDoubleSpinBox 实际上 派生自它自己的 QAbstractSpinBox。但事实并非如此。此外,QObject 会失败,因为 Qt 似乎在那里做了很多 static_cast upwarts,这也不适用于虚拟继承。 我还考虑通过将 AbstractRevertibleSpinBox 设为模板类来解决它,该模板类将不同的旋转框类型作为模板类参数传递。构造将如下所示:

template<class Base>
class AbstractRevertibleSpinBox:
    public Base {};

class RevertibleSpinBox:
    public AbstractRevertibleSpinBox<SpinBox> { };

这会起作用,但是 Qt 的 moc 对模板类非常不满意。因此,例如,我无法连接模板类中的任何信号和插槽。至少不使用传统的基于字符串的 SIGNAL()/SLOT() 语法。

有没有其他合理优雅的方法来克服这个问题..?

最佳答案

正如我前面评论中指出的那样,我认为这是 Decorator Pattern 的一个明显案例如果你想要一个易于扩展的功能系统,否则只需从 QObject 继承而不是从具有几乎相同代码的基本“接口(interface)”。

我将从恕我直言的更糟糕的方法开始,在给出的其他答案中提供:

  • 子类化每个旋转框

这显然很烦人,但更重要的是,您将无法支持任何 QSpinBox 子类,因为您总是需要为每次添加创建一个新的子类。这只是一种不灵活的方法。

  • 有一个包含按钮和旋转框的父部件

这看起来像是两个不同事物的不必要耦合,因此如果您稍后通过按钮以外的任何其他方式触发它们,您将无法轻松地重用旋转框。我认为这两个概念应该保持区别并分开管理。

此外,dynamic_casting 是错误的,因为您应该使用 qobject_cast(如果有的话)。

让我们仔细看看装饰器方法:

enter image description here

这还不是您案例的解决方案,但它很好地展示了如何将功能添加(即“装饰”)到现有层次结构中。为了更具体地了解您的用例,让我们看看在您的特定场景中会发生什么:

  • 组件:QAbstractSpinBox

  • 混凝土构件

    • QSpinBox
    • QDoubleSpinBox
    • QDateTime编辑
      • QDateEdit
      • QTimeEdit
  • 装饰器:AbstractSpinBoxDecorator(您的情况可以省略此步骤)

  • 具体装饰器:RevertibleSpinBoxDecorator

让我们亲自动手实现这个设计:

main.cpp

#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>

class RevertibleSpinBoxDecorator : public QAbstractSpinBox
{
    Q_OBJECT
public:
    explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QAbstractSpinBox *parent = Q_NULLPTR)
        : QAbstractSpinBox(parent)
        , m_abstractSpinBox(abstractSpinBox)
    {
    }

public slots:
    void revert(bool)
    {
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
        if (spinBox) {
            spinBox->setValue(spinBox->minimum());
            return;
        }

        QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
        if (doubleSpinBox) {
            doubleSpinBox->setValue(doubleSpinBox->minimum());
            return;
        }

        QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
        if (dateEdit) {
            dateEdit->setDate(dateEdit->minimumDate());
            return;
        }

        QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
        if (timeEdit) {
            timeEdit->setTime(timeEdit->minimumTime());
            return;
        }

        QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
        if (dateTimeEdit) {
            dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
            return;
        }

        Q_ASSERT_X(false, "decorator", "concrete component unimplemented");
    }

protected:
    void showEvent(QShowEvent *event) Q_DECL_OVERRIDE
    {
        m_abstractSpinBox->show();
        event->ignore();
        hide();
    }

private:
     QAbstractSpinBox *m_abstractSpinBox;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
        {
            connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
            QHBoxLayout *layout = new QHBoxLayout(centralWidget);
            layout->addWidget(revertibleSpinBoxDecorator);
            layout->addWidget(pushButton);
            setCentralWidget(centralWidget);
        }

    private:
        QWidget *centralWidget{new QWidget(this)};
        QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
        RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
        QPushButton *pushButton{new QPushButton(this)};
};

#include "main.moc"

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    MainWindow mainWindow;
    mainWindow.show();
    return application.exec();
}

如果你想摆脱 QAbstractSpinBox 继承,你将需要应用更多的胶水和恕我直言,这并没有太大的好处,同时失去了灵 active 。你会从这样的事情开始:

非装饰者

#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>

class RevertibleSpinBoxDecorator : public QObject
{
    Q_OBJECT
public:
    explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QObject *parent = Q_NULLPTR)
        : QObject(parent)
        , m_abstractSpinBox(abstractSpinBox)
    {
    }

public slots:
    void revert(bool)
    {
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
        if (spinBox) {
            spinBox->setValue(spinBox->minimum());
            return;
        }

        QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
        if (doubleSpinBox) {
            doubleSpinBox->setValue(doubleSpinBox->minimum());
            return;
        }

        QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
        if (dateEdit) {
            dateEdit->setDate(dateEdit->minimumDate());
            return;
        }

        QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
        if (timeEdit) {
            timeEdit->setTime(timeEdit->minimumTime());
            return;
        }

        QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
        if (dateTimeEdit) {
            dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
            return;
        }

        Q_ASSERT_X(false, "strategy", "strategy not implemented");
    }

private:
     QAbstractSpinBox *m_abstractSpinBox;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
        {
            connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
            QHBoxLayout *layout = new QHBoxLayout(centralWidget);
            layout->addWidget(doubleSpinBox);
            layout->addWidget(pushButton);
            setCentralWidget(centralWidget);
        }

    private:
        QWidget *centralWidget{new QWidget(this)};
        QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
        RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
        QPushButton *pushButton{new QPushButton(this)};
};

#include "main.moc"

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    MainWindow mainWindow;
    mainWindow.show();
    return application.exec();
}

主程序

TEMPLATE = app
TARGET = main
QT += widgets
CONIG += c++11
SOURCES += main.cpp

构建并运行

qmake && make && ./main

关于c++ - 扩展公共(public)基础 : Diamond inheritance vs. QObject,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27449517/

相关文章:

c++ - 用于通过 char 快速访问 glpyh 纹理的数据结构

c++ - 如何检测来自不同类的 QGraphicsItem 和 QGraphicsPixmapItem 图像之间的冲突

c++ - 如何使用 C++ 从 Windows 注册表中获取 "Open With"程序及其完整路径?

c++ - 当我按下回车键或在其区域外单击鼠标时,如何从可编辑的 QComboBox 中删除光标?

xml - log4j 可以从基本/根元素继承 xml 吗?

c# - 使用泛型 C# 访问 const

c++ - windows下如何正确使用共享内存

python - 从 python float 转换为 C++ double 时的精度损失

c++ - 扩展QTextDocument中 block 的定义

java - Java继承的另一个问题?