如何更好地实现我的C++设计模式
How to better implement my C++ design pattern?
我查找了我的设计可能导致的所有不同的编译器错误。所有的答案和修复都是有道理的,但我已经到了修复一件事会引发另一件坏事的地步。
我的C++项目越来越大,所以我试图概括我的问题,以从经验丰富的C++开发人员那里受益。
我正在编写的软件是一个XML解析器,它在运行时创建UI对象。我为我想要使用的不同类型的容器设计了ContainerInterface
。例如,TabWidget
是子类QTabWidget
,并且它也继承了ContainerInterface
。目前,它们是AbsoluteWidget
、TreeWidget
和TabWidget
。所有这些都实现了ContainerInterface
:中定义的以下纯虚拟函数
virtual PushButton* createButton(const QString& label, const QString& define, const QPoint& topLeft, const QSize& size) = 0;
virtual CheckBox* createCheckBox(const QString& label, const QString& define, const QString& header, const QPoint& topLeft, const QSize& size) = 0;
virtual ComboBox* createComboBox(const QString& label, const QString& define, const QString& header, const QPoint& topLeft, const QSize& size) = 0;
virtual Image* createImage(const QString& file, const QString& define, const QPoint& topLeft, const QSize& size) = 0;
virtual Led* createLed(const QString& define, const QString& onColor, const QString& offColor, const QPoint& topLeft, const QSize& size) = 0;
virtual Text* createText(const QString& define, const QString& label, const QPoint& topLeft, const QSize& size) = 0;
所以在解析器中,我可以使用ContainerInterface
,例如:
void XmlReader::readCheckBox(ContainerInterface* container, const QString& header)
{
Q_ASSERT(xml.isStartElement() && xml.name() == "checkbox");
QXmlStreamAttributes attr = xml.attributes();
CheckBox* checkBox = container->createCheckBox(getLabel(attr), getDefine(attr), getHeader(attr, header), getTopLeft(attr), getSize(attr));
m_centralWidget->setUIElement(getDefine(attr), checkBox); //this is why i need a return value anyway
}
这为我节省了很多代码,而且效果很好。所以我希望ContainerInterface
也有:
virtual TabWidget* createTabWidget(const QPoint& topLeft, const QSize& size) = 0;
virtual TreeWidget* createTreeWidget(const QStringList& labels, const QPoint& topLeft, const QSize& size) = 0;
现在我们来谈谈我遇到困难的部分:这需要在TabWidget
中实现createTabWidget
等等(这很好,因为我可以在Tabwidget
中包含Tabwidget
,它本身也包含在另一个TabWidget
中)。如果我使用与其他元素(例如CheckBox
)相同的设计,这将返回一个指向新TabWidget
:的指针
TabWidget* TabWidget::createTabWidget(const QPoint& topLeft, const QSize& size)
{
return new TabWidget(topLeft, size);
}
这样做会让我很难调试,所以这引发了几个问题:
- 上
TabWidget::createTabWidget
可能吗?(如果没有这些,它会变得很好) - 我是否应在容器界面中包括容器的文件,例如
tabwidget.h
,以避免循环依赖?(这给了我expected class name before '{' token
) - 那么我需要在
TreeWidget
中转发声明TabWidget
吗?(这给我一个invalid use of incomplete type
错误)
看起来缺少一个基本概念,即声明和定义的分离。
你的.h文件应该包含一个类定义。因此TabWidget.h应该包含类TabWidget等。相应的方法在.cpp文件中定义。
因此,TabWidget.h不需要PushButton的实现。它只使用指针PushButton*
。这意味着编译器只需要知道PushButton
是一个类类型:class PushButton;
。然而,TabWidget.cpp
很可能正在调用new Pushbutton
,为此,您需要在TabWidget.cpp
中包含PushButton.h
。
因此,您可以看到不存在循环依赖关系。依赖关系是定向的:.cpp文件依赖于.h文件,但反之亦然。
您正在使用ContainerInterface
来创建GUI组件,因此不要通过向TabWidget
类添加createTabWidget()
方法来破坏概念。
在create...()
方法中引入父参数。默认情况下,它可以是nullptr。
接口:
virtual TabWidget* createTabWidget(const QPoint& topLeft, const QSize& size, Component* parent = nullptr) = 0;
用法:
// Create a top-level tab widget, parent is null.
TabWidget* outerWidget = container->createTabWidget(position, size);
// Create a child tab widget, set the parent.
TabWidget* innerWidget = container->createTabWidget(position, size, outerWidget);
该解决方案假设所有GUI组件(TabWidget
、ComboBox
、Image
…)都派生自一个公共基类,在我的示例中是Component
。
这需要在TabWidget中实现createTabWidget,依此类推
这是错误的。要使用对大多数类型的指针和引用,不需要类型的实现是可见的,甚至不需要类型声明,只需要转发声明。
因此,以下是您的项目可能看起来的样子:
// ContainerInterface.h
#ifndef ContainerInterface_h
#define ContainerInterface_h
// No includes necessary
class QString;
class PushButton;
class TabWidget;
class ContainerInterface {
public:
virtual PushButton* createButton(const QString &) = 0;
virtual TabWidget* createTabWidget(const QString &) = 0;
};
#endif // ContainerInterface_h
// TabWidget.h
#ifndef TabWidget_h
#define TabWidget_h
#include <QTabWidget>
#include "ContainerInterface.h"
class TabWidget : public QTabWidget, ContainerInterface {
...
};
#endif // TabWidget_h
// TabWidget.cpp
#include "TabWidget.h" // must always be the first file included!
#include "PushButton.h"
TabWidget * TabWidget::createTabWidget(const QString & foo) {
...
}
PushButton * TabWidget::createPushButton(const QString & foo) {
...
}
这会编译,但这并不意味着它是一个好的设计。容器接口必须知道如何创建给定类型的实例,这是非常糟糕的。ContainerInterface
的想法似乎从根本上被打破了,无法扩展。
你应该扭转这个问题:
有一个小部件创建者的映射,这些创建者从XML标记或类映射到接受XML流并返回QWidget*
指针的函子。每个具体的小部件类只需要在该映射中注册即可。
例如:
// ItemFactory.h
#ifndef ItemFactory_h
#define ItemFactory_h
#include <QMap>
class QXmlStreamReader;
class ItemFactory {
QMap<QString, QWidget*(*)(QXmlStreamReader &)> m_loaders;
public:
QWidget * loadItem(const QString & tag, QXmlStreamReader & reader);
void registerLoader(const QString & tag, QWidget*(*loader)(QXmlStreamReader &));
static ItemFactory & instance(); // if you want it a singleton
};
#endif // ItemFactory_h
// ItemFactory.cpp
#include "ItemFactory.h"
Q_GLOBAL_STATIC(ItemFactory, itemFactory)
QWidget * ItemFactory::loadItem(const QString & tag, QXmlStreamReader & reader) {
auto it = m_loaders.find(tag);
if (it == m_loaders.end())
return nullptr;
return (*it)(reader);
}
void ItemFactory::registerLoader(const QString & tag, QWidget*(*loader)(QXmlStreamReader &) {
m_loaders.insert(tag, loader);
}
ItemFactory & ItemFactory::instance() {
return *itemFactory;
}
// CheckBox.h
...
class CheckBox : public QCheckBox {
public:
...
static void registerType();
}
// CheckBox.cpp
#include "CheckBox.h"
#include "ItemFactory.h"
void CheckBox::registerType() {
ItemFactory::instance().registerLoader(QStringLiteral("CheckBox"), +[](QXmlStreamReader & xml) -> QWidget* {
Q_ASSERT(xml.isStartElement() && xml.name() == "checkbox");
auto attr = xml.attributes();
auto checkBox = new CheckBox(getLabel(attr), getDefine(attr), getHeader(attr, header), getTopLeft(attr), getSize(attr));
checkBox.setProperty("define", getDefine(attr)); // bundle the define in the widget in a generic way
});
}
因此,允许容器获取指向通用QWidget*
的指针并添加它应该很容易,使用widget->property("define").toString()
来获得您可能需要的"定义"。
与许多其他问题一样,你的主要问题是没有说明你的目的。是的,你有一个定义UI的XML文件,但它还没有强制执行特定的C++设计。
- 如何修复我的快速排序实现?
- 在我的四叉树实现中遇到问题
- 如何为我的类实现/重载二进制运算符
- 在我的trie实现中出现分段错误
- 我无法让我的合并排序实现运行
- 如何为我的容器实现随机访问迭代器?
- 我的堆栈弹出式磁带的实现是否泄漏内存?
- 如何在C++中围绕任意值实现我所谓的"wraparound sort"?
- 为什么在这种情况下不调用我的虚拟函数实现?
- 为什么在我的实现中,所有数组都对齐到 16 个字节?
- 如何测试我的谷歌身份验证器实现?
- 在我自己的堆栈中实现top的问题
- 当给出预先排序的输入时,为什么我的快速排序实现很慢
- 当我从头文件和实现文件调用我的函数到我的主文件时,我没有得到任何输出
- 如何实现我的自定义范围循环
- 如何更好地实现我的C++设计模式
- 无法实现我的简单朋友函数C++
- 我应该在哪里实现我的类方法
- 我应该使用Try Catch块来实现我的业务逻辑吗?
- 我应该如何实现我的c++异常