我想我在这里遇到了一种钻石继承问题。
Qt提供了几个旋转框,用于整数值,用于双精度数以及日期/时间。它们都来自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()
。例如,对setValue(double)
使用QDoubleSpinBox
或setDate(QDate)
使用QDateEdit
。
然后我以明显的方式为我需要的所有旋转框派生了相应的类,如下所示:
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()
语法。
还有其他合理优雅的方法来克服这个问题吗?
答案 0 :(得分:7)
正如我在评论中所提到的那样,如果你想要一个易于扩展的特征系统,我认为这是Decorator Pattern的一个明显的例子,否则只是继承QObject而不是从基础&#34;接口&# 34;几乎相同的代码。
我将从IMHO更糟糕的方法开始,在其他答案中提供:
这显然很无聊,甚至更重要的是,你将无法支持任何QSpinBox子类,因为你总是需要为每次添加创建一个新的子类。这只是一种不灵活的方法。
这看起来像两个不同东西的不必要的耦合,因此如果您通过按钮之后以任何其他方式触发它们,您将无法轻松地重复使用旋转框。我认为这两个概念应该保持不同并单独管理。
此外,dynamic_cast
错误的建议是错误的,因为您应该使用qobject_cast
。
让我们仔细看看装饰者的方法:
这还不是您的案例的解决方案,但它很好地展示了如何将功能添加到现有层次结构中(即#34;装饰&#34;)。为了更加具体地了解您的用例,让我们看看在您的特定场景中会出现什么:
组件:QAbstractSpinBox
具体组件
装饰者:AbstractSpinBoxDecorator(在你的情况下可以省略这一步)
Concrete Decorator:RevertibleSpinBoxDecorator
让我们亲自动手实施这个设计:
#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
继承,你将需要使用更多的胶水和恕我直言,以获得不多的收益,同时失去灵活性。你会从这样的事情开始:
#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
答案 1 :(得分:1)
我认为我根本不会使用继承;相反,我会使用合成:创建一个不相关的类(例如从QWidget派生),它使用QBoxLayout(或类似的)将旋转框和按钮排列为子窗口小部件。可以将适当的spinbox对象指针传递给类的构造函数,该构造函数也会执行必要的connect()命令来来回转发各种信号。当然,你可能想重新声明类中QAbstractSpinBox类的插槽/信号/方法的等价物,但好处是它适用于任何QAbstractSpinBox子类。
(至于让revert()做正确的事情,最简单的方法可能只是使用dynamic_cast&lt;&gt;的一些丑陋的特殊情况逻辑 - 因为你只需要支持三个QAbstractSpinBox子类,那将是可管理的,至少那种丑陋隐藏在私有方法体内,而不是暴露给类的用户)
答案 2 :(得分:1)
要考虑的一个选项可能是简单地创建从QSpinBox
,QDoubleSpinBox
和QDateEdit
中派生的类,然后将公共代码提取到函数中。
以QSpinBox
为例:
class RevertibleSpinBox : public QSpinBox
{
public:
RevertibleSpinBox(QWidget* parent) : QSpinBox(parent)
{
RevertibleSpinBoxHelpers::installRevertAction(lineEdit(), this);
}
public slots:
void revert()
{
setValue(0);
}
// etc.
};
namespace RevertibleSpinBoxHelpers
{
void installRevertAction(QLineEdit* target, QObject* handler)
{
QAction* revertAction = new QAction(handler);
target->addAction(revertAction, QLineEdit::TrailingAction);
QObject::connect(revertAction, SIGNAL(triggered()), handler, SLOT(revert()));
}
}
免责声明:如果您的需求比您在问题中提到的要复杂得多,那么这可能不是最佳方法。
答案 3 :(得分:0)
IMO你不必要地使你的生活变得复杂。甚至装饰器模式都是矫枉过正,更不用说那些不必要的继承和设计问题了。您所需要的只是一个漂亮的辅助功能:
void revert(QAbstractSpinBox * box) {
QSpinBox * spin = qobject_cast<QSpinBox *>(box);
if (spin) { spin->setValue(spin->minimum()); return; }
QDateTimeEdit * dt = qobject_cast<QDateTimeEdit *>(box);
if (dt) { dt->setDateTime(dt->minimumDateTime()); return; }
// and so on...
qDebug() << "you should not be seeing this";
return;
}
如果你想要在spinbox旁边实例化一些东西,那么将它包装为Reverter
对象的静态方法。
如果你把它放在QObject
派生类中,你可以把它作为一个插槽,但这真的不是必需的,因为在Qt中你可以连接到非自由函数和方法{{1派生类。或者,如果您愿意,可以直接从插槽中调用它:
QObject
这类似于装饰器模式,但它确实带来了一些优点:
public slots:
void revertSpinBoxA() { revert(spinBoxA); }
void revertSpinBoxB() { revert(spinBoxB); }
来恢复任意数量的旋转框,装饰器需要n个装饰器来容纳n个旋转框,它会耗尽更多的内存,这可能不是致命的但是不必要开销,因为revert方法中的类型转换使得实际的装饰器完全冗余。与装饰器的不同之处在于,您将使用原始对象+每个装饰器作为对象的控制器,而在此解决方案中,您仍然使用实际对象,并仅将其传递给{{1}为了额外的功能。减少工作量,减少代码量,减少复杂性,减少使用的内存。