Come definire una struttura per memorizzare alcune classi generiche "Variabili" per un facile accesso in seguito

0

Sto incontrando un problema di progettazione. Il mio codice è in C # ma i concetti potrebbero applicarsi a qualsiasi linguaggio OO.

Sto progettando un framework per eseguire esperimenti, e questi esperimenti hanno diverse variabili che manipoleremo, e diverse variabili che calcoleremo dopo la manipolazione (come le colonne in un foglio Excel). Ogni variabile può avere un certo numero di valori possibili. Ho bisogno di memorizzare queste variabili in qualche modo in un ordine particolare. Quindi, avrò bisogno di creare una riga di dati (una riga dell'ipotetico foglio excel) usando l'ordine definito, dove scelgo un singolo valore di ogni variabile e calcola alcuni dei valori delle variabili.

EDIT: Sulla base dei commenti sembra che ho bisogno di spiegare lo scopo. Gli utenti dovrebbero essere in grado di inserire il loro design sperimentale, cioè quali variabili vogliono e quali sono i livelli di ciascuna variabile. Quindi il mio programma effettua ogni permutazione di quelle (prove), randomizza le prove e poi le itera attraverso l'esperimento. L'output è tutto solo una stringa .csv. Quindi, dopo ogni prova, emette il valore di ogni variabile di input, quindi calcola e restituisce le variabili di output.

Ho tutto funzionante al momento. Ma al momento, ho bisogno di definire le variabili manualmente per gli utenti, e quindi possono mettere un elenco di valori in ciascuno per conto proprio. Devo anche scrivere manualmente le conversioni alle stringhe per ogni variabile e quindi inserirle manualmente nell'ordine e allineamento corretti per il file csv. Il mio obiettivo finale è di consentire loro di creare le proprie variabili e aggiungerle all'esperimento senza il mio intervento, e imporre un'attribuzione dell'output della stringa.

cosa ho:

List<float> levels = new List<float>();
levels.add(1f);
levels.add(2f);
levels.add(3f);
Experiment.variable1.levels = levels;

cosa voglio:

Experiment.AddVariable(new Variable<float>(levels);
// note that Variable should conceivably be any type
// and all variables types should have a method to output its value as a 
// string depending on what type it is.

Quindi avrò qualcosa come: inputvariable1 denominato "distance" ha valori int :

1, 2

inputvariable2 denominato "time given" ha valori float :

1.1, 1.2, 1.3

inputvariable3 denominato "some unknown class" ha alcuni valori definiti dall'utente:

CustomClass.OnOffEnum.ON, CustomClass.OnOffEnum.OFF

ouputvariable1 denominato "tempo di risposta" ha valori float:

Quindi ho una variabile di classe

public class Variable<T> {
    string name
    T value
}

Ora, ho bisogno di memorizzare in qualche modo queste variabili, così ho creato una classe Row:

public class Row {
    List<Variable> variables;

    public Row(List<Variable> variables) {
        this.variables = variables
    }
}

Ora ho un paio di problemi:

1. Come posso definire una struttura per le righe per ogni esperimento, in modo che tutte le righe di un particolare esperimento abbiano le stesse variabili nello stesso ordine? (stessi nomi, valori diversi).

Per affrontare questo problema ho preso in considerazione la modifica di Row per richiedere un oggetto RowSchema immesso per forzarlo a definire un determinato schema per le sue variabili. Ma ora ho indovinato quale dovrebbe essere la classe RowShema .

2. Come posso garantire che ciascuna variabile implementa un metodo StringOutput() che posso utilizzare per scrivere in un file di testo?

Ho preso in considerazione di forzare il tipo generico T di Variable per implementare un'interfaccia con questo metodo, ma voglio Variable per poter essere anche un tipo primitivo.

qualcosa come

public class Variable<T> where T : StringOutputter {

Ho preso in considerazione anche solo l'utilizzo di ToString() , ma poi per i tipi personalizzati, non posso garantire che sarà un formato definito.

3. Sarebbe bello in altre parti del mio programma essere in grado di riferirsi al valore di una particolare variabile (per nome) di una particolare riga. Qualcosa di simile:

row2.<<variablename>>.value

Ho riflettuto su questo per diversi giorni, ma non riesco a capire l'architettura corretta.

La mia attuale implementazione consiste nel definire manualmente ogni Row con i campi che rappresentano ciascuna variabile che voglio. Ma questo non è estendibile dal momento che ho bisogno di ridefinire la classe Row per ogni esperimento.

    
posta Adam B 20.11.2018 - 20:34
fonte

1 risposta

1

Ci sono diversi modi per farlo. Qual è il migliore è in qualche modo il linguaggio che si utilizza, le librerie che hai, e ciò che ha senso per i clienti di questa struttura dei dati.

In sostanza stai chiedendo un tavolo. Potrei suggerire di provare una delle centinaia di motori di database là fuori. Tutto ciò che funziona con i vari ADO, DAO, System.Data, o anche database interni dell'applicazione come SQLite . Ciascuno avrà un supporto diverso per i tipi di dati C #, ce ne potrebbe essere uno che soddisfi i tuoi requisiti.

Se non vuoi fare affidamento su una libreria di terze parti per qualsiasi motivo, o se non soddisfa le tue esigenze. Mi vengono in mente due approcci.

  1. Utilizza uno schema
  2. Utilizza un prototipo

Uno schema in parole povere è un oggetto che codifica le informazioni di "tipo" per la tua riga / campo, di solito uno schema per ogni oggetto creato (quindi uno schema di riga che contiene N schemi di campo). Essenzialmente ogni schema è un factory Row / Field. Su richiesta può creare un nuovo oggetto riga / campo, con tutto istanziato nell'ordine corretto. Potrebbe anche fornire una convalida, ecc. Le righe / Campi possono avere un riferimento al loro schema specifico (utile per molti scopi). La tabella consentirebbe solo uno schema. E potresti supportare l'assegnazione a livello di tabella da una tabella con uno schema superset a una tabella con uno schema di sottoinsiemi (uno schema con lo stesso o meno campi, con tipi uguali o più generali, ecc ...).

Un approccio prototipo sfrutta il concetto di clonazione. Si crea una riga che contiene tutte le informazioni sul tipo e i valori predefiniti. Ogni volta che hai bisogno di una nuova riga, chiami clone () su questo oggetto. Una tabella dovrebbe essere composta da numerose righe e ogni riga potrebbe avere un numero diverso di campi e tipi da qualsiasi altra riga nella tabella. La tabella ha probabilmente una funzione isHomogenus () che controlla che ogni riga abbia lo stesso numero di campi, con il valore di ogni campo che condivide lo stesso tipo di ogni altro campo nella stessa colonna.

Ci sono due modi per la clonazione del prototipo e influenzano il modo in cui il sistema si evolve, questi sono: clonazione profonda e clonazione superficiale.

Un clone profondo crea un duplicato completo della riga e dei campi, ogni valore valutato viene copiato, tutte le informazioni sul tipo ecc. se modifichi una riga, l'altra rimane inalterata.

Un clone di Shallow crea ancora un duplicato della riga e dei campi, ma non copia direttamente i valori. Invece mantiene un riferimento alla riga / campo originale e un delta (le differenze tra esso e il prototipo). Quando viene richiesto un valore, o qualche altra informazione (interrogata), la cercherà nel delta restituendo quella risposta se trovata. Se non viene trovato, chiederà l'originale. Quando il clone viene aggiornato, cambierà il delta. Ciò significa anche che le modifiche alla riga originale si rifletteranno in ogni riga clonata che non è stata aggiornata a qualcosa di diverso. Questo è approssimativamente il Modello di proprietà .

Come è ovvio, entrambi i tipi di clonazione hanno vantaggi e svantaggi in base al modo in cui si desidera che questa tabella si comporti. La clonazione superficiale consente di eseguire catene di aggiornamenti come un albero, la clonazione profonda non consente gli aggiornamenti concatenati e semplifica l'aggiunta di righe simili.

L'approccio dello schema e l'approccio del prototipo hanno alcune aree di sovrapposizione in termini di come sono implementati come uno schema di riga / campo come una fabbrica è funzionalmente simile al metodo clone di un prototipo. La differenza è che un prototipo non è diverso da qualsiasi altra riga, mentre uno schema è fondamentalmente diverso.

Per un'implementazione rigorosa, preferisci l'approccio dello schema con metodi di fabbrica e di convalida. Per un approccio più pigro preferisco la prototipazione.

Se si desidera avere il meglio di tutto, è possibile utilizzare sia gli schemi che la prototipazione, tuttavia si tenga presente che tale sistema avrà compromessi in termini di conformità dello schema, flessibilità del prototipo e / o sforzo di tenuta contabile. È la vecchia questione della tipizzazione statica rispetto alla tipizzazione dinamica rispetto al costo.

    
risposta data 22.11.2018 - 04:07
fonte

Leggi altre domande sui tag