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

时间:2016-03-30 09:16:49

标签: c++ qt

我查看了我的设计似乎导致的所有不同编译器错误。所有的答案和修复都是有道理的,但我达到了一个点,修复一件事会引发另一件坏事。

我的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;

现在我们遇到了困难的部分:这需要在createTabWidget中实施TabWidget等等(这很好,因为我可以有一个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. 我是否需要在TabWidget中转发声明TreeWidget? (这给了我invalid use of incomplete type错误)

3 个答案:

答案 0 :(得分:1)

看起来你错过了一个基本概念,那就是宣言和定义的分离。

您的.h文件应包含一个类定义。所以TabWidget.h应该包含类TabWidget等。相应的方法在.cpp文件中定义。

因此,TabWidget.h不需要实现PushButton。它只使用指针PushButton*。这意味着编译器只需要知道PushButton是类类型:class PushButton;。但是,TabWidget.cpp调用new Pushbutton的可能性非常大,为此您需要在PushButton.h中加入TabWidget.cpp

所以你看到没有循环依赖。依赖关系是方向性的:.cpp文件依赖于.h文件,但反之亦然。

答案 1 :(得分:0)

您正在使用ContainerInterface创建GUI组件,因此请不要通过向createTabWidget()类添加TabWidget方法来破坏概念。

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示例

答案 2 :(得分:0)

  

这需要在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*指针的仿函数。每个具体的小部件类只需要在该地图中注册。

E.g:

// 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()来获取&#34;定义&#34;你可能需要的。

与许多其他问题一样,您的主要问题并不是说明您的目的是什么。是的,你有一个定义UI的XML文件,但是它还没有强制执行特定的C ++设计。