2016-03-30 3 views
-1

Я искал все ошибки компилятора, которые, по-видимому, вызывает мой дизайн. Все ответы и исправления имеют смысл, но я дошел до того, что исправление одной вещи вызывает еще одну плохую вещь.Как лучше реализовать мой шаблон дизайна C++?

Мой проект на C++ становится все больше и больше, поэтому я пытаюсь обобщить свою проблему, чтобы воспользоваться опытными разработчиками на C++.

Программное обеспечение, которое я пишу, представляет собой XML-парсер, который создает объекты пользовательского интерфейса во время выполнения. Я разработал 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; 

А теперь мы подошли к той части, где я с трудом: это потребуется осуществление 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)
+0

Парсер XML, который вы ищете, поставляется с Qt ('QUiLoader'), и вы можете использовать его во время выполнения. Почему вы пишете еще один? См. [Этот ответ] (http://stackoverflow.com/a/19327470/1329652) для полного примера. –

+0

У вас, похоже, много методов, созданных вручную для конкретного класса. Они не нужны. Вы можете использовать механизм 'QMetaObject' для автоматического определения аргументов конструктора класса для данного типа' QObject' и автоматически сопоставить их с XML. –

+0

Несмотря на то, что я сомневаюсь, что любой мог угадать все цели разбора xml для создания объектов пользовательского интерфейса, на это так много ответов. Я иду: потому что это моя работа. – tobilocker

ответ

1

Похоже, что вам не хватает базовой концепции, и это разделение декларации и определения.

Ваши файлы .h должны содержать одно определение класса. Поэтому TabWidget.h должен содержать класс TabWidget и т. Д. Соответствующие методы определены в файле .cpp.

Из-за этого TabWidget.h не нуждается в реализации PushButton. Он использует только PushButton*, указатель. Это означает, что компилятор просто должен знать, что PushButton является типом класса: class PushButton;. Однако вполне возможно, что TabWidget.cpp звонит new Pushbutton, и для этого вам необходимо включить PushButton.h в TabWidget.cpp.

Итак, вы видите, что нет циклических зависимостей.Зависимости являются направленными: файлы .cpp зависят от файлов .h, но не наоборот.

+0

Спасибо за то, что мои приведенные примеры могут запутать читателя, но это не так. 'Ваши файлы .h должны содержать одно определение класса' -> они делают,' Соответствующие методы определены в .cpp файле' -> они есть. Как я уже сказал, теперь есть проблема с любыми объектами, такими как 'PushButton' и' CheckBox', но с объектами, которые могут содержать обезьяны, в моем случае, указанными как 'Conatiners' E.g. 'TabWidget'. Просто потому, что 'PushButton', имеющий другой' PushButton', бессмыслен, но 'TabWidget' с' TabWidget' не является. Итак, проблемы начинаются с них. Все остальное отлично работает – tobilocker

+0

@tobilocker: Ваши пронумерованные вопросы 2 и 3 определенно не о классах, содержащих «себя». Но даже для тех, нет проблем, если вы сохраните определение класса в заголовке и реализации в файле .cpp. Вы можете настаивать на том, что это неверно, и проблемы действительно начинаются с них, но есть миллионы программистов на С ++, у которых нет проблем с этим. – MSalters

+0

На самом деле я рад это слышать, и я ожидал, что это будет общий шаблон дизайна. Этот комментарий уже является отличным советом, потому что он говорит, что моя общая идея для дизайна возможна и распространена. Вероятно, недостаток появляется где-то еще в конце. Обычно я бы сделал домашнее задание, прежде чем ставить здесь такой общий вопрос, но у меня никогда не было такого количества ошибок при пробном и ошибочном подходе. Поэтому спасибо за ответ, и я обновлю вопрос, как только увижу недостаток. – tobilocker

0

Вы используете свой ContainerInterface для создания компонентов графического интерфейса, поэтому не нарушайте концепцию, добавив метод 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 (TabWidget, ComboBox, Image, ...) все полученные от общего базового класса, Component в моем примере.

+0

Все они получены из 'QWidget', что хорошо, потому что оно позволяет использовать любые формы вложений UI Elements в контейнерах. Но это не позволяет мне иметь общий базовый класс, как было предложено, так как, например, 'QTabWidget' и' QTreeWidget' являются подклассами 'QWidget' и я подклассифицирую их на' TabWidget' и 'TreeWidget'. Но это позволяет мне передавать любой тип как 'QWidget' в качестве родителя (который я действительно делал для каждого объекта). Например, в реализации' createCheckBox' в 'AbsoluteWidget' (просто' QWidget') я делаю: ' вернуть новый CheckBox (label, define, header, topLeft, size, this); '. – tobilocker

+0

Дело в том, что 'Button' и' CheckBox' (и так далее) не наследуют 'ContainerInterface', поэтому это просто, но' TreeWidget' и 'TabWidget' do – tobilocker

+0

Я понимаю. Итак, если 'TabWidget' получен из' ContainerInterface', я думаю, что это хорошая идея (несмотря на то, что я сказал ранее), чтобы иметь метод «TabWidget :: createTabWidget». Просто используйте * forward декларации * в * ContainerInterface.h *, и не включайте в него заголовки компонентов gui. – Tomas

0

это потребуется осуществление createTabWidget в TabWidget и так далее

Это ложь. Чтобы использовать указатели и ссылки для большинства типов, вам не нужно, чтобы реализация типа была видимой или даже декларацией типа, но только объявление вперед.

Итак, следующее, как ваш проект может выглядеть:

// 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(), чтобы получить «определить», что Вы, вероятно, нужно.

Ваша основная проблема, как это часто встречается во многих других вопросах, заключается не в том, какова ваша цель. Да, у вас есть XML-файл, который определяет пользовательский интерфейс, но это еще не навязывает определенный дизайн C++.

+0

Спасибо за ваш ответ lehgthily. Я должен признать, что это не может быть большим вопросом, и мое кодирование очень сложно. Все остальное, что я хотел бы сказать, это просто чистый сарказм. – tobilocker

Смежные вопросы