web-dev-qa-db-ja.com

シリアル化、視覚化、計算をGUI編集可能なコードオブジェクトから分離する方法は?

これはX Y問題かもしれませんが、これが私の状況です。

QT5 C++コードベースを持っています。そのタスクは、「プロセスチェーン」の構成を可能にすることです。ここでの「プロセスチェーン」とは、単一の入力を受け取り、チェーン内の次の「プロセスエレメント」に単一の出力を返す、順次実行されるリンクされた要素のリストを意味します。チェーンの最後に、このデータが保存されます。

各プロセス要素は構成可能であり、それぞれが同じデータで機能し、同じデータ型を出力しますが、それぞれは完全に異なる何かを実行します。それらは非常に異なる構成パラメーターを取りますが、同じインターフェースを共有します。それぞれが異なるコンストラクターを持ち、1つはそのコンストラクターで1つの浮動小数点数、もう1つは行列などを取ることができます。

ソフトウェアの反復ごとに成長するプロセス要素タイプは数十あります。各プロセス要素タイプは(現在はJSONを介して)シリアル化する必要があり、構成をguiを介して構成する必要があります。構成する各パラメーターには同じguiウィジェットタイプがない場合があります(つまり、行編集のみが必要な場合があり、別の場合ラジオボタン、別のスピンボックスが必要な場合がありますが、すべて同じプロセス要素を構成します)。

私の問題は、現在これを非常に結合された方法で達成していることです。プロセス要素クラスは、独自のJSON形式、およびjsonからそれ自体を構築する方法を知って定義する必要があり、本質的に、クラス名->コンストラクター、およびクラス名-> json形式のグローバルな静的な順不同マップに自分自身を追加する必要があります。

現在、GUIのタイプを指定する機能を追加する方法はありません。そのため、現在の方法は持続可能ではないと考えました。

現在のアプローチに関する私の問題は次のとおりです。

  • シリアル化がクラス自体で機能する方法を組み合わせます。
  • 暗黙的にディスプレイを結合します(現在、すべてのGUIタイプは同じですが、要件が変更され、一部のGUIタイプが異なる必要があります)。

この機能を切り離したいのですが、他の可能な解決策には満足していません。各プロセス要素の「シリアライゼーションクラス」を作成すると、コードのにおいのように見え、視覚化のためにこれを複製する必要があります。また、クラスごとに複製する必要のある作業は行わないようにします。現在、グローバルクラスマップへのクラス名マッピングの静的な追加を自動化するマクロを設定しています。

以下は、説明のためにGUIがどのように見えるかのモックアップです: enter image description here

デザインカップリングの問題を解決するためにどのような戦略を採用できますか? 最終的に、私の状況でシリアル化と視覚化を「コードオブジェクト」(プロセス要素)から効果的に分離できる方法はありますか?

3
whn

あなたの問題を理解しているように、各プロセス要素のインターフェースは、読み取りおよび書き込み可能なプロパティのセットです。 _(name, value)_ペアのリストは、GUIとシリアル化の両方に必要なすべてです。 Qtはこれを行うためのフレームワークを提供します-_Q_PROPERTY_およびQVariantベースのセッターおよびゲッターを使用できます。個人的には、表示/シリアル化に必要なプロパティに関する情報を提供するプロセスステップの基本クラスを作成することから始めます。

_class BaseProcessElement : public QObject
{
    Q_OBJECT

public:
    // Returns type name used for serialization.
    virtual QString typeName() const = 0;

    // Returns a list of properties to be displayed/serialized.
    virtual QStringList elementProperties() const = 0;
};

class SomeProcessElement : public BaseProcessElement
{
    Q_OBJECT

public:
    Q_PROPERTY(int some_prop_1 READ someProp1 WRITE setSomeProp1 NOTIFY someProp1Changed);
    Q_PROPERTY(QString some_prop_2 WRITE someProp2 WRITE setSomeProp2 NOTIFY someProp2Changed);

    int someProp1() const;
    QString someProp2() const;

    QString typeName() const override;

    QStringList elementProperties() const override
    {
        return {"some_prop_1", "some_prop_2"};
    }

public slots:
    void setSomeProp1(int value);
    void setSomeProp2(const QString& value);

signals:
    void someProp1Changed(int newValue);
    void someProp2Changed(const QString& newValue);
};
_

これで、エレメントクラスでの追加のサポートなしで、GUIおよびシリアル化に関連するプロパティをクエリできます。

_void createUI(BaseProcessElement* element)
{
    for(auto propName : element->elementProperties())
    {
        QVariant propValue = element->property(propName.toStdString().c_str());

        if(propValue.canConvert<int>())
            addSpinBox(propName, propValue.toInt());
        else if(propValue.canConvert<QString>())
            addLineEdit(propName, propValue.toString());
        else....
    }
}
_

(明らかに、より多くの可能なプロパティタイプがある場合、ファクトリの方が良いでしょう-これは単にアイデアを説明するためです)

プロパティは、逆シリアル化/編集中にelement->setProperty(propName, propValue);を呼び出して設定することもできます。

プロパティに値以外のプロパティがある場合(たとえば、数値の境界)、値を他のデータとグループ化するタイプを定義できます。

_class IntElementProperty : public ElementProperty
{
public:
    int value() const;
    int maxValue() const;
    int minValue() const;
    ...
 };
_

そして、Qtのプロパティアクセサーを通じてそれらのクラスのインスタンスを返します。

3
joe_chip

あなたが取ることができる1つのアプローチは、別個の仕様言語を持つことです。仕様言語は、詳細タイプと表示タイプによって各パラメーターを指定します。たとえば、0.1〜1.0の範囲で0.1ずつ増加するフィールドがあり、スライダーとして入力するのが最適な場合があります。検証の他の部分も指定できます。

仕様に従って、ボイラープレートの大部分をバイパスして、要素のシリアライザー/デシリアライザーコードを生成できます。同様に、GUIは仕様を直接読み取り、GUIの説明を使用して要素のパラメーターを視覚的に表すことができます。

たとえば:

element Baz:
  field foo: float, min(0.1), max(1.0), increment(0.1), slider;
  field bar: string, maxlen(10), minlen(1), match("[a-z]+");
end element

このようなミニチュア言語の作成は常に行われています。 Lua、PythonまたはLISPで言語を書くことができるので、インタープリターを書く必要はありません。

1
BobDalgleish

私の問題を一般的に理解する方法は、コードモデルを採用する必要があるということです。

すべてをAPIとしてプログラムする

より具体的かつ要点として、JSONシリアル化用の汎用ライブラリを開発する必要があります。

JSONコンテナー/ APIを構築する

c ++コードのすべてのJSON要素を保持します。エルゴ、あなたはこのようなものを握ることができなければなりません:

enter image description here

qt指向のAPI。ここでツールを使用することもできます https://doc.qt.io/qt-5/json.html 、ただし、独自に作成する場合、各レイヤーを基本的に設定されたQObjectとして解釈しますQMap<QString,QJSONContainer>、jsonデータ型が列挙子として設定されています。

私はQVariantを使用しません。一般的に、必要以上に多くの型があるためです。

いずれにせよ、次のようなことができるはずです。

QJSONContainer json(QFile("/foo/bar.json")); // Reads from file, creates object api
json["orders"]["custid"].toInt(); // 11045
json["orders"]["fname"].toInt();  // throws error because it is a string
json["orders"]["fname"].setToString("bar"); // changes its type, emits a signal
connect(&json["orders"]["fname"], &QJSONContainer::typeChanged,
[&](QJSONContainer::Type type) {
        switch (type) { 
        case JSONContainer::Null:            break;
        case JSONContainer::Array:  doFoo(); break;
        case JSONContainer::String: doBar(); break;
        default: break;
        }        
});
json.save(); // overwrites old json file 

これで、JSON APIができました。必要に応じて、コンソールアプリケーションで厳密に使用できます。

GUIインタープリターを作成します。

QWidget QJSONINterpreter::getWidget(QJSONInterpreter::GUIType type)
{
        switch (type) { 
        case JSONInterpreter::Matrix: return getMatrix();
        case JSONInterpreter::Tree:   return getTree();
        default: /* Throw an error */;
        }          
}

したがって

QJSONInterpreter jsonGui(json.toUtf8()); // Takes a Json QByteArray
jsonGui.getWidget(QJSONInterpreter::Matrix).show(); // Constructs a matrix from it

物事が分離され、今後のプロジェクトで使用できるAPIができました。

0
Akiva