2016-03-30 16 views
-1

私のデザインが原因と思われるさまざまなコンパイラエラーをすべて調べました。すべての答えと修正は理にかなっていますが、1つのことを修正することで別の悪いことが起こるという点に達しました。私のC++デザインパターンをより良く実装するには?

私のC++プロジェクトはますます大きくなってきています。そのため、経験豊富なC++開発者の恩恵を受けるために問題を一般化しようとしています。

私が書いているソフトウェアは、実行時にUIオブジェクトを作成する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 
} 

これは私のコードと作品の多くを保存しましたいいね

virtual TabWidget* createTabWidget(const QPoint& topLeft, const QSize& size) = 0; 

virtual TreeWidget* createTreeWidget(const QStringList& labels, const QPoint& topLeft, const QSize& size) = 0; 

をそして今、我々は、私は苦労してる部分に来る:だから私も持っているContainerInterfaceをしたいと思いますので、細かいである(ように、これはTabWidgetcreateTabWidgetの実装が必要になりますし、私は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. TabWidgetTreeWidgetに転送する必要がありますか? (これは私にinvalid use of incomplete typeエラーを与えます)
+0

あなたが探しているXMLパーサにはQt( 'QUiLoader')が付いており、実行時に使用することができます。なぜあなたは別のものを書いていますか?完全な例については、[この回答](http://stackoverflow.com/a/19327470/1329652)を参照してください。 –

+0

手動で作成されたクラス固有のファクトリメソッドがたくさんあるようです。それらは不要です。 'QMetaObject'機構を利用して、与えられた' QObject'型のクラスコンストラクタ引数を自動的に決定し、それらを自動的にXMLにマップすることができます。 –

+0

UIオブジェクトを作成するためにxmlを解析する目的を誰もが推測できるのは間違いですが、これには多くの回答があります。私は一緒に行く:それは私の仕事なので。 – tobilocker

答えて

1

あなたは基本的な概念が欠けているように見えます、それは宣言と定義の分離です。

.hファイルには1つのクラス定義が含まれている必要があります。そのため、TabWidget.hにはTabWidgetなどのクラスが含まれているはずです。対応するメソッドは.cppファイルで定義されています。

このため、TabWidget.hにはPushButtonの実装が必要ありません。それはPushButton*、ポインタを使用します。つまり、コンパイラはPushButtonがクラスタイプであることを知る必要があります。class PushButton;。ただし、TabWidget.cppnew Pushbuttonを呼び出している可能性があります。そのためにはPushButton.hTabWidget.cppに含める必要があります。

循環的な依存関係はありません。依存関係は方向性があります:.cppファイルは.hファイルに依存しますが、その逆はありません。

+0

私の与えられた例が読者を混乱させる可能性があることを指摘してくれてありがとうが、これは当てはまらない。 'あなたの.hファイルには一つのクラス定義が含まれていなければなりません.' - >'対応するメソッドは.cppファイルに定義されています ' - >そうです。私が言ったように、 'PushButton'や' CheckBox'のようなオブジェクトには問題がありますが、私の場合は 'Conatiners'と呼ばれるオブジェクトを含むオブジェクトがあります。 'TabWidget'。他の 'PushButton'を持つ' PushButton'は無意味ですが、 'TabWidget'を保持する' TabWidget'はそうではないからです。だから、問題はそれらから始まります。他のすべてはうまく動作します。 – tobilocker

+0

@tobilocker:あなたの番号付き質問2と3は、自分自身を「含む」クラスではありません。しかし、それらの場合でも、ヘッダーにクラス定義を保持し、.cppファイルに実装すると問題はありません。あなたはそれが間違っていると主張するかもしれません、そして問題はそれらから実際に始まりますが、これに問題のない何百万人ものC++プログラマーがいます。 – MSalters

+0

実際これを聞いてうれしいですし、私はそれが一般的なデザインパターンであると思っていました。このコメントは、デザインに関する私の一般的な考え方が可能で一般的だと言っているので、すでに大きなアドバイスです。おそらく、この欠陥は最後のどこかに現れるでしょう。通常、私はここでそのような一般的な質問をする前に宿題をしていただろうが、私は試行錯誤のアプローチでそれほど多くの種類のエラーがあったことはなかった。答えに感謝し、私は欠陥を見つけたらすぐに質問を更新します。 – tobilocker

0

GUIコンポーネントの作成にはContainerInterfaceを使用していますので、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です。

+0

これらはすべて 'QWidget'から派生しています。これは、コンテナ内に任意の形式の入れ子UI要素を許可するためです。しかし、それは私が提案する共通の基底クラスを持つことを許さない。 'QTabWidget'と' QTreeWidget'はサブクラス化された 'QWidget'sです。それらを' TabWidget'と 'TreeWidget'にサブクラス化します。しかし、それは私があらゆるタイプの 'QWidget'を親として渡すことを可能にします(実際にはすべてのオブジェクトに対して)。例として、' AbsoluteWidget'(単に 'QWidget')の' createCheckBox'の実装では、新しいCheckBox(ラベル、定義、ヘッダー、topLeft、サイズ、this)を返します。 – tobilocker

+0

'Button'と' CheckBox'(など)は 'ContainerInterface'を継承しないので、簡単ですが、' TreeWidget'と 'TabWidget'は – tobilocker

+0

私は理解します。したがって、 'TabWidget'が' ContainerInterface'から派生している場合は、 'TabWidget :: createTabWidget'メソッドを持つことが良いアイデアだと思います。あなたの* ContainerInterface.h *で* forward宣言*を使用し、そこにあなたのGUIコンポーネントヘッダーを含めないでください。 – Tomas

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ストリームを受け取り、QWidget*ポインタを返すファンクタにXMLタグやクラスからマップウィジェットの作成者のマップを持っています。すべての具体的なウィジェットクラスは、単にそのマップに登録する必要があります。

例えば:

// 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++デザインを強制するものではありません。

+0

おかげさまで、ありがとうございました。私はこれが大きな疑問ではないかもしれないし、私のコーディングが非常に洗練されていることを認めなければならない。私が言いたいことは、純粋な皮肉だけです。 – tobilocker

関連する問題