Design pattern per valori interdipendenti

9

Riepilogo: esiste un buon modello di progettazione per ridurre la duplicazione delle informazioni tra valori strettamente interdipendenti?

Nella mia linea di lavoro è abbastanza comune avere una relazione tra quantità tale da poter ricavare una delle quantità se si conoscono gli altri. Un esempio potrebbe essere la legge sui gas ideali :

Pv = RT

Potresti immaginare di creare una classe per rappresentare lo stato di un gas ideale. La classe avrebbe 3 proprietà, naturalmente Pressure , Temperature e SpecificVolume ciascuna di un tipo appropriato.

Per l'utente di un oggetto di questa classe, sembrerebbe naturale aspettarsi che se imposti valori per Pressure e Temperature , potresti quindi leggere un valore per SpecificVolume e aspettarti che l'oggetto l'ho calcolato per te.

Allo stesso modo, se imposti valori per Pressure e SpecificVolume , puoi quindi leggere Temperature , ecc.

Per implementare effettivamente questa classe, tuttavia, è necessaria una duplicazione delle informazioni. Dovresti programmare esplicitamente tutte le variazioni dell'equazione, trattando una variabile diversa come dipendente in ciascun caso:

  1. T = P * v / R

  2. P = R * T / v

  3. v = R * T / P

che sembra violare il principio ASCIUTTO . Sebbene ognuno di essi esprima la stessa relazione, questi casi richiedono una codifica e un'elaborazione indipendenti. test.

In casi reali la logica a cui sto pensando è più complessa di questo esempio, ma presenta lo stesso problema di base. Quindi ci sarebbe un valore reale se potessi esprimere la logica solo una volta, o almeno un paio di volte.

Si noti che una classe come questa probabilmente dovrebbe anche occuparsi di assicurarsi che sia stata inizializzata correttamente prima che i valori vengano letti, ma penso che sia una considerazione secondaria.

Sebbene abbia fornito un esempio matematico, la domanda non è limitata solo alle relazioni matematiche tra i dati. Sembrava essere un semplice esempio per chiarire il punto.

    
posta DaveInCaz 21.07.2017 - 20:10
fonte

9 risposte

12

Non vi è alcun problema da una prospettiva OO. Potresti avere un metodo di offerta GasLaw della classe statica

GetVolume(R, T, P) 
GetPressure(R, T, v)

et cetera

e non ci sarebbe un problema di duplicazione. Non ci sono oggetti, nessun membro dei dati. Solo comportamenti tutti diversi.

Potresti considerare la legge sul gas come "una cosa sola" ma le operazioni sono tutte distinte. Non c'è niente di sbagliato nell'avere un metodo per ciascuno dei casi d'uso della legge.

    
risposta data 21.07.2017 - 21:00
fonte
10

Questo non è realmente possibile nel modo in cui lo chiedi.

Considera: ho un oggetto gas ideale g . Se imposto esplicitamente tutti e tre la temperatura, la pressione e il volume specifico, e quindi ottieni di nuovo la temperatura, come:

IdealGas g;
g.setTemperature(t1);
g.setPressure(p1);
g.setVolume(v1);
assert(g.getTemperature() == what); // ?

dovrebbe:

  1. dammi il valore t1 originariamente impostato?
  2. calcola il valore usando la pressione e il volume specifico?
  3. calcola il valore usando la pressione e il volume specifico solo quando imposto questi dopo la temperatura?
  4. dammi il valore originale se è abbastanza vicino a quello calcolato (dovremmo consentire gli errori di arrotondamento) e calcolare come # 2 o # 3 altrimenti?

Se devi fare questo, devi tenere traccia di molti stati per ogni membro: non è inizializzato, impostato esplicitamente dal codice cliente o memorizzato nella cache di un valore calcolato? Il valore impostato esplicitamente o memorizzato nella cache è stato invalidato da un altro membro impostato successivamente?

Il comportamento è chiaramente difficile da prevedere dalla lettura del codice client. Questo è un progetto scadente, perché sarà sempre difficile capire perché hai ottenuto la risposta che hai fatto. La complessità è un segno che questo problema è inadatto per OO, o almeno che un oggetto gas statico ideale è una cattiva scelta di astrazione.

    
risposta data 25.07.2017 - 18:35
fonte
6

Per prima cosa, non penso che tu abbia violato il Principio ASCIUTTO perché tutte e 3 le formule calcolano valori diversi. Il fatto che sia una dipendenza tra tutti e 3 i valori è perché la vedi come un'equazione matematica e non come un'assegnazione di variabile programmatica.

Suggerisco di implementare il tuo caso con una classe immutabile IdealGas come segue

public class IdealGas {
    private static final double R = 8.3145;

    private final Pressure            pressure;
    private final Temperature         temperature;
    private final SpecificVolume      specificVolume;

    public IdealGas(Pressure pressure, Temperature temperature)
    {
        this.pressure = pressure;
        this.temperature = temperature;
        this.specificVolume = calculateSpecificVolume(pressure.getValue(), temperature.getValue());
    }


    public IdealGas(Pressure pressure, SpecificVolume specificVolume)
    {
        this.pressure = pressure;
        this.specificVolume = specificVolume;
        this.temperature = calculateTemperature(pressure.getValue(), specificVolume.getValue());
    }

    public IdealGas(Temperature temperature, SpecificVolume specificVolume)
    {
        this.temperature = temperature;
        this.specificVolume = specificVolume;
        this.pressure = calculatePressure(temperature.getValue(), specificVolume.getValue());
    }

    private SpecificVolume calculateSpecificVolume(double pressure, double temperature)
    {
        return new SpecificVolume(R * temperature / pressure);
    }

    private Temperature calculateTemperature(double pressure, double specificVolume)
    {
        return new Temperature(pressure * specificVolume / R);
    }

    private Pressure calculatePressure(double temperature, double specificVolume)
    {
        return new Pressure(R * temperature / specificVolume);
    }

    public Pressure getPressure()
    {
        return pressure;
    }

    public Temperature getTemperature()
    {
        return temperature;
    }

    public SpecificVolume getSpecificVolume()
    {
        return specificVolume;
    }
}

Lascia che ti spieghi l'implementazione. La classe IdealGas ha incapsulato le 3 proprietà Pressure, Temperature e SpecificVolume come valori finali per l'immutabilità. Ogni volta che chiami getXXX non verrà eseguito alcun calcolo. Il trucco è avere 3 costruttori per tutte e 3 le combinazioni di 2 parametri. In ogni costruttore si calcola la terza variabile mancante. Il calcolo viene eseguito una volta, in fase di costruzione e assegnato al terzo attributo. Poiché questo è fatto nel costruttore, il terzo attributo può essere definitivo e immutabile. Per immutabilità presumo che le classi Pressure, Temperature e SpecificVolume siano anche immutabili.

L'unica parte rimanente è avere getter per tutti gli attributi. Nessun setter esiste perché si passano i parametri ai costruttori. Se è necessario modificare un attributo, creare una nuova istanza IdealGas con i parametri desiderati.

Nel mio esempio, le classi Pressure, Temperature e SpecificVolume sono semplici wrapper con un doppio valore. Il codice di esempio è in java, ma può essere generalizzato.

Questo approccio può essere generalizzato, passare tutti i dati correlati in un costruttore, calcolare i dati relativi nel costruttore e avere solo getter.

    
risposta data 04.10.2017 - 12:11
fonte
4

Questa è una domanda davvero interessante! In linea di principio, hai ragione di duplicare le informazioni perché la stessa equazione è utilizzata in tutti e tre i casi, solo con diverse incognite.

Ma in un tipico linguaggio di programmazione mainstream non esiste un supporto integrato per risolvere le equazioni. Le variabili in programmazione sono sempre quantità conosciute (al momento dell'esecuzione), quindi, nonostante le somiglianze superficiali, le espressioni nei linguaggi di programmazione non sono paragonabili alle equazioni matematiche.

In un'applicazione tipica, un'equazione come la descrivi tu verrebbe semplicemente scritta come tre espressioni separate e tu vivrai con la duplicazione. Ma esistono librerie per la risoluzione di equazioni che potrebbero essere utilizzate in questo caso.

Questo è più di un modello, è un intero paradigma di programmazione, chiamato limitazione dei vincoli, e ci sono linguaggi di programmazione dedicati come Prolog per questo tipo di problemi.

    
risposta data 25.07.2017 - 19:48
fonte
3

Questa è un'irritazione comune quando si codificano modelli matematici in software. Entrambi usano una notazione molto simile per modelli semplici come la legge sul gas, ma i linguaggi di programmazione non sono matematica e molte cose che puoi tralasciare mentre lavori nel mondo della matematica devono essere fatte esplicitamente nel software.

Non si sta ripetendo. Non esiste alcun concetto di equazione, trasformazione algebrica o "soluzione per x" nella maggior parte dei linguaggi di programmazione. Senza una rappresentazione software di tutti questi concetti, non c'è modo per il software di arrivare a T = P * v / R data l'equazione Pv = RT .

Anche se può sembrare una limitazione irragionevole dei linguaggi di programmazione quando si guardano modelli semplici come la legge sui gas, anche i calcoli matematici leggermente più avanzati consentono equazioni che non hanno soluzioni algebriche chiuse o nessun metodo noto che possa derivare in modo efficiente la soluzione. Per l'architettura del software non si può dipendere da esso nel caso più complesso.

E il semplice caso da solo? Non sono sicuro che varrebbe la pena di leggere le specifiche della funzione nel linguaggio di programmazione. I modelli tendono a essere sostituiti, non modificati e di solito hanno solo poche variabili da risolvere.

    
risposta data 22.07.2017 - 11:10
fonte
1

The class would have 3 properties, naturally Pressure, Temperature, and SpecificVolume.

No, ne bastano due. Ma rendili privati e esponi i metodi come pressure() , temperature() e specificVolume() . Uno di questi, non corrispondente alle due proprietà private, dovrebbe avere la logica appropriata. Quindi possiamo eliminare la duplicazione dei dati.

Ci dovrebbero essere tre costruttori con parametri (P, T) , (P, v) e (T, v) . Uno di essi, corrispondente alle due proprietà private, agisce semplicemente come setter. Gli altri due dovrebbero avere la logica appropriata. Ovviamente la logica è scritta tre volte (due volte qui e una volta nel paragrafo precedente), ma non sono duplicati. Anche se li consideri come duplicati sono necessari.

Are these three expressions express the same relationship?

Sì matematicamente e no OO-ly. Negli oggetti OO i cittadini di prima classe non sono espressioni. Per rendere le espressioni cittadini di prima classe (o per rendere la relazione espressa dalla stessa cosa) dobbiamo scrivere una classe, diciamo, Expression o Equation che non è un compito facile (potrebbero esserci alcune librerie).

The question is not limited only to mathematical relationships among data.

La relazione non è anche un cittadino di prima classe. Per questo potremmo aver bisogno di scrivere una classe Relationship che non è neanche facile. Ma vivere con i duplicati è più facile.

Se decidi di vivere con i duplicati e se i parametri nella relazione sono alti, considera l'utilizzo del modello Builder . Anche se i parametri sono meno, considera l'utilizzo di Fabbriche statiche per evitare limitazioni di sovraccarico costruttore.

    
risposta data 26.07.2017 - 20:02
fonte
1

You could imagine creating a class to represent the state of an ideal gas.

Una classe dovrebbe rappresentare il comportamento di un gas ideale. Un'istanza di classe - un oggetto - rappresenta lo stato.

Questo non è il prelievo di lendini. La progettazione della classe dovrebbe essere dal punto di vista del comportamento e della funzionalità. Il comportamento è esposto pubblicamente in modo tale che un oggetto istanziato (sì, è superfluo) raggiunge uno stato attraverso l'esercizio del comportamento.

Gas.HeatToFarenheit( 451 );

E il seguente è semplicemente un'assurdità e impossibile nel mondo reale. E non farlo nel codice:

Gas.MoleculeDistiance = 2.34;  //nanometers

Il comportamento esegue il rendering della "domanda di informazioni duplicate"

L'influenza delle stesse proprietà di stato con metodi diversi non è duplicazione. La seguente influenza anche la temperatura, ma non è un duplicato di quanto sopra:

Gas.PressurizeTo( 34 );  //pascals
    
risposta data 27.07.2017 - 22:04
fonte
0

Probabilmente potresti usare la generazione del codice per generare le varie forme dell'equazione dal singolo modulo specificato.

Posso immaginare che sarebbe abbastanza complicato per formule più complesse. Ma per quello semplice che devi dare solo

  • sposta le variabili da una parte all'altra per divisione
  • capovolgi i lati sinistro e destro dell'equazione

Posso immaginare se aggiungi la moltiplicazione, l'addizione e la sottrazione a quell'elenco e che la tua formula base abbia ciascuna variabile solo una volta, quindi puoi usare la manipolazione delle stringhe per generare automaticamente tutte le versioni della formula con il codice boilerplate appropriato per usare quello corretto dato le variabili conosciute.

Wolfram Alpha ha vari strumenti di manipolazione delle equazioni, per esempio.

Tuttavia !! a meno che tu non abbia grandi elenchi di queste classi per produrre non riesco a vedere tale codice come un uso efficace del tuo tempo.

Devi solo generare questo codice una volta per funzione e le tue funzioni sono probabilmente troppo complesse per essere risolte semplicemente come il tuo esempio.

La codifica manuale di ciascuna versione è probabilmente il metodo più rapido e affidabile per generare le funzioni e sebbene sia possibile immaginare una soluzione in cui si codifichi per stimare in modo iterativo i valori e verificare la verità, penso che fare necessità di generare e compilare le funzioni per calcolare la variabile sconosciuta con una quantità ragionevole di potenza di elaborazione.

    
risposta data 22.07.2017 - 01:24
fonte
0

Penso che stai pensando troppo a questo, prendi in considerazione la seguente soluzione:

double computeIdealGasLaw(double a, double b, double c){
    return a * b / c;
}
// example
//T = P * v/R
double P = ....;
double v = ....;
// R is a constant;
double T = computeIdealGasLaw(P, v, R); 


//P = R * T/v
double T = ....;
double v = ....;
// R is a constant;
double P = computeIdealGasLaw(R, T, v); 

//v = R * T/P
double T = ....;
double P = ....;
// R is a constant;
double v = computeIdealGasLaw(R, T, P); 

Hai solo bisogno di definire una funzione, nessuna duplicazione, potresti anche usarla per un'implementazione di una classe interna, oppure potresti semplicemente usare questa funzione per cambiare solo i parametri che usi dove.

Puoi utilizzare lo stesso modello anche per qualsiasi situazione in cui hai una funzione in cui i valori vengono scambiati.

Se usi una langauge tipizzata staticamente e devi gestire una funzione in cui i valori possono essere di tipo diverso, puoi usare la programmazione dei template (nota, usando la sintassi C ++)

template<class A, class B, class C, class D>
D computeIdealGasLaw(A a, B b, C c){
    return D(a * b / c);
}
    
risposta data 04.10.2017 - 19:11
fonte

Leggi altre domande sui tag