Come separare la serializzazione, la visualizzazione e il calcolo dagli oggetti di codice modificabili della GUI?

1

Questo potrebbe essere un problema di X Y, ma qui c'è la mia situazione.

Ho una base di codice QT5 C ++ il cui compito è quello di consentire la configurazione di una "catena di processi". Una "catena di processi" qui significa una lista di elementi collegati che eseguono in ordine sequenziale, prendendo un singolo input e restituendo un singolo output al successivo "elemento di processo" nella catena. Alla fine della catena, questi dati vengono salvati.

Ogni elemento del processo è configurabile, e mentre ciascuno lavora sugli stessi dati ed emette lo stesso tipo di dati, ognuno fa qualcosa di completamente diverso sotto. Prendono parametri di configurazione molto diversi, ma condividono la stessa interfaccia. Ognuno ha un costruttore diverso, uno può prendere un singolo float, un altro una matrice, ecc ... nel suo costruttore.

Ci sono dozzine di tipi di elementi del processo che crescono ad ogni iterazione del software. Ogni tipo di elemento del processo deve essere serializzato (attualmente tramite JSON) e ha bisogno che la sua configurazione sia configurata attraverso una GUI, a cui ogni parametro da configurare potrebbe non avere lo stesso tipo di widget GUI (ad esempio, potrebbe essere necessaria solo una modifica riga, un'altra potrebbe aver bisogno di pulsanti di opzione, un altro di una casella di selezione, ma tutti configurano lo stesso elemento di processo).

Il mio problema è che attualmente lo realizzo in modo molto accoppiato. La classe dell'elemento del processo deve conoscere e definire il proprio formato JSON, come costruirsi da json e essenzialmente deve aggiungersi a una mappa globale non ordinata di classname - > costruttori e classname - > formati json.

Al momento non ho un modo per aggiungere la possibilità di specificare il tipo di GUI, questo è quello che mi ha portato a credere che il metodo attuale sia insostenibile.

I miei problemi con il mio approccio attuale sono i seguenti:

  • Unisco il modo in cui la serializzazione funziona con la classe stessa.
  • Accetto implicitamente il display (attualmente tutti i tipi di gui sono uguali, ma ora i requisiti sono cambiati e richiedono alcuni tipi di gui per essere diversi).

Voglio accoppiare questa funzionalità, ma non sono soddisfatto di altre possibili soluzioni. La creazione di una "classe di serializzazione" per ogni elemento del processo sembra odore di codice, e questo dovrebbe essere duplicato per la visualizzazione. Vorrei anche evitare di fare un lavoro che dovrebbe essere replicato per ogni classe. Attualmente ho una macro per automatizzare l'aggiunta statica di mapping di nomi di classe alle mappe di classi globali.

Ecco una parte di ciò che il gui ha bisogno di chiarimenti:

Qualistrategiepossoimpiegareperrisolvereilproblemadiaccoppiamentodiprogettazionechestoavendo?Indefinitiva,esisteunmodoperseparareefficacementelaserializzazioneelavisualizzazionedagli"oggetti codice" (elementi del processo) nella mia circostanza?

    
posta opa 14.05.2018 - 22:58
fonte

2 risposte

3

Come ho capito il tuo problema, l'interfaccia di ogni elemento del processo è un insieme di proprietà leggibili e scrivibili. Un elenco di coppie (name, value) è tutto ciò che è necessario per la GUI e la serializzazione. Qt ha fornito un framework per farlo: puoi usare Q_PROPERTY e QVariant setter e getter basati. Personalmente, inizierei creando una classe base per una fase del processo che fornisce informazioni su quali proprietà sono necessarie per la visualizzazione / serializzazione:

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);
};

Ora puoi interrogare le proprietà rilevanti per la GUI e la serializzazione senza alcun supporto aggiuntivo nella classe element:

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....
    }
}

(Ovviamente, se ci sono molti più tipi di proprietà possibili, una fabbrica sarebbe meglio - questo è solo per illustrare l'idea)

Le proprietà possono anche essere impostate durante la deserializzazione / modifica chiamando element->setProperty(propName, propValue); .

Se una proprietà ha più proprietà di un valore (ad esempio i limiti per i numeri), puoi definire un tipo che raggruppa un valore con altri dati:

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

E restituisci le istanze di tali classi tramite gli accessor di proprietà di Qt.

    
risposta data 16.05.2018 - 01:22
fonte
1

Un approccio che potresti fare è avere un linguaggio specifico per le specifiche. Il linguaggio delle specifiche dovrebbe specificare ciascun parametro, per tipo dettagliato e per tipo di presentazione. Ad esempio, potresti avere un campo che varia tra 0,1 e 1,0 di 0,1 incrementi, e potrebbe essere inserito come slider. Altre parti della convalida potrebbero essere specificate pure.

In base alle specifiche, è possibile generare il codice serializzatore / deserializzatore per l'elemento, ignorando gran parte della piastra della caldaia. Allo stesso modo, la GUI può leggere direttamente la specifica e utilizzare la descrizione della GUI per rappresentare visivamente il parametro dell'elemento.

Come per esempio:

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

La creazione di linguaggi in miniatura come questa viene eseguita in ogni momento. Puoi scrivere la lingua in Lua, Python o Lisp in modo da non dover scrivere un interprete.

    
risposta data 14.05.2018 - 23:29
fonte

Leggi altre domande sui tag