如何更好地实现我的C++设计模式

How to better implement my C++ design pattern?

本文关键字:实现 我的 C++ 设计模式 何更好 更好      更新时间:2023-10-16

我查找了我的设计可能导致的所有不同的编译器错误。所有的答案和修复都是有道理的,但我已经到了修复一件事会引发另一件坏事的地步。

我的C++项目越来越大,所以我试图概括我的问题,以从经验丰富的C++开发人员那里受益。

我正在编写的软件是一个XML解析器,它在运行时创建UI对象。我为我想要使用的不同类型的容器设计了ContainerInterface。例如,TabWidget是子类QTabWidget,并且它也继承了ContainerInterface。目前,它们是AbsoluteWidgetTreeWidgetTabWidget。所有这些都实现了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);
}

这样做会让我很难调试,所以这引发了几个问题:

  1. TabWidget::createTabWidget可能吗?(如果没有这些,它会变得很好)
  2. 我是否应在容器界面中包括容器的文件,例如tabwidget.h,以避免循环依赖?(这给了我expected class name before '{' token
  3. 那么我需要在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组件(TabWidgetComboBoxImage…)都派生自一个公共基类,在我的示例中是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++设计。