Entwurfsmuster für voneinander abhängige Werte

8

Zusammenfassung: Gibt es ein gutes Entwurfsmuster, um Doppelarbeit von Informationen zwischen eng voneinander abhängigen Werten zu reduzieren?

In meiner Arbeit ist es ziemlich üblich, eine Beziehung zwischen Mengen zu haben, so dass Sie eine der Mengen ableiten können, wenn Sie die anderen kennen. Ein Beispiel könnte das ideale Gasgesetz sein :

Pv = RT

Sie können sich vorstellen, eine Klasse zu erstellen, die den Zustand eines idealen Gases darstellt. Die Klasse würde 3 Eigenschaften hat, natürlich Pressure, Temperatureund SpecificVolumejeder einen entsprechenden Typen.

Für den Benutzer eines Objekts dieser Klasse erscheint es natürlich zu erwarten, dass Sie, wenn Sie Werte für beide Pressureund festlegen Temperature, einen Wert für auslesen SpecificVolumeund erwarten können, dass das Objekt diesen für Sie berechnet hat.

Wenn Sie Werte für beide Pressureund festlegen SpecificVolume, können Sie auch Temperatureusw. auslesen .

Um diese Klasse tatsächlich zu implementieren, sind jedoch einige Informationen doppelt vorhanden. Sie müssten alle Variationen der Gleichung explizit programmieren und jeweils eine andere Variable als abhängig behandeln:

  1. T = P * v / R

  2. P = R * T / v

  3. v = R * T / P

das scheint das DRY-Prinzip zu verletzen . Obwohl jeder die gleiche Beziehung ausdrückt, erfordern diese Fälle eine unabhängige Codierung und Prüfung.

In realen Fällen ist die Logik, an die ich denke, komplexer als dieses Beispiel, weist jedoch das gleiche Grundproblem auf. Es wäre also ein echter Wert, wenn ich die Logik nur einmal oder zumindest weniger oft ausdrücken könnte.

Beachten Sie, dass eine Klasse wie diese wahrscheinlich auch sicherstellen muss, dass sie ordnungsgemäß initialisiert wurde, bevor Werte ausgelesen werden, aber ich denke, dass dies eine sekundäre Überlegung ist.


Obwohl ich ein mathematisches Beispiel gegeben habe, beschränkt sich die Frage nicht nur auf mathematische Beziehungen zwischen Daten. Das schien nur ein einfaches Beispiel zu sein, um den Punkt zu verdeutlichen.

UuDdLrLrSs
quelle
5
Beachten Sie, dass Ihr Beispiel keine Duplizierung aufweist. DRY gilt nur, wenn Sie das gleiche Verhalten aktiv neu erstellen . In Ihrem Beispielfall wird jede Variation der Gleichung nach einem anderen Wert aufgelöst - und ist daher ein anderes Verhalten. Die Pv = RT-Form der Gleichung ist aus OO-Sicht eine nutzlose Verallgemeinerung für sich.
T. Sar
Ich sehe keine Vervielfältigung.
Hören Sie auf, Monica
1
Es gibt auch das Problem, "illegale" Staaten unmöglich zu machen, dh solche mit Pv/T != R. Ich bin mir nicht sicher, ob diese Idee Ihnen bei der Lösung der zugrunde liegenden Probleme helfen könnte oder ob dies sie sogar kompliziert.
Bernhard Hiller
Wie gesagt, T. Sar gibt es hier keine Wiederholung. Alles andere, was Martin Maat vorschlug, wird sicherlich mehr Ärger hervorrufen, als es lösen wird. Grundsätzlich ist Ihre Suche nach einer Engine, die eine Formel automatisch anpassen kann, um den in einer anderen fehlenden Wert zu berechnen, die Mühe nicht wert, es sei denn, Sie finden eine Bibliothek, die dies tut (wer weiß ...). Geben Sie das einfach auf.
Walfrat
Wie die Antworten zeigten, haben imperative Sprachen einige Probleme. Im Gegensatz dazu macht eine deklarative / logische Sprache wie Prolog es trivial. Überprüfen Sie Wikipedia . Es kann mit einer imperativen Sprache gemacht werden, es erfordert "nur" mehr Arbeit.
Armaghast

Antworten:

12

Aus OO-Sicht gibt es kein Problem. Sie könnten eine statische Klasse haben, die GasLaw-Methoden anbietet

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

und so weiter

und es würde kein Problem mit der Vervielfältigung geben. Es gibt kein Objekt, keine Datenelemente. Nur Verhaltensweisen, die alle unterschiedlich sind.

Sie können das Gasgesetz als "eine Sache" betrachten, aber die Operationen sind alle unterschiedlich. Es ist nichts Falsches daran, für jeden Anwendungsfall des Gesetzes eine Methode zu haben.

Martin Maat
quelle
3
Dies befasst sich nicht wirklich mit der Frage, wie intern in einer solchen Klasse Doppelarbeit von Informationen / Logik reduziert werden kann. Die externe Schnittstelle ist eigentlich kein Problem.
UuDdLrLrSs
Ich bin nicht der Meinung, dass die Frage nicht angesprochen wird. Die Antwort ist mir klar, dass Martin sagt, tu nicht, was du fragst, wie es geht, weil es eindeutig viel einfacher zu verstehen ist, wie man genau dasselbe erreichen kann.
Dunk
10

Dies ist nicht wirklich möglich, wie Sie fragen.

Bedenken Sie: Ich habe ein ideales Gasobjekt g. Wenn ich explizit alle drei Werte für Temperatur, Druck und spezifisches Volumen einstelle und dann die Temperatur erneut erhalte , wie z.

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

sollte es:

  1. Gib mir den Wert, den t1ich ursprünglich eingestellt habe?
  2. Berechnen Sie den Wert anhand des Drucks und des spezifischen Volumens?
  3. Berechnen Sie den Wert nur mit Druck und spezifischem Volumen , wenn ich diese nach der Temperatur einstelle .
  4. Geben Sie mir den ursprünglichen Wert, wenn er nahe genug an dem berechneten Wert liegt (wir sollten Rundungsfehler berücksichtigen), und berechnen Sie andernfalls als # 2 oder # 3?

Wenn Sie dies tun müssen, müssen Sie für jedes Mitglied eine Menge Status nachverfolgen: Ist es nicht initialisiert, explizit durch den Clientcode festgelegt oder wird ein berechneter Wert zwischengespeichert? Wurde der explizit festgelegte oder zwischengespeicherte Wert von einem anderen Mitglied, das später festgelegt wird, ungültig gemacht?

Das Verhalten ist offensichtlich schwer vorherzusagen, wenn der Client-Code gelesen wird. Dies ist ein schlechtes Design, da es immer schwierig sein wird herauszufinden, warum Sie die Antwort erhalten haben, die Sie erhalten haben. Die Komplexität ist ein Zeichen dafür, dass dieses Problem nicht zu OO passt oder dass ein zustandsbehaftetes ideales Gasobjekt eine schlechte Wahl für die Abstraktion ist.

Nutzlos
quelle
8

Erstens glaube ich nicht, dass Sie gegen das DRY-Prinzip verstoßen, da alle drei Formeln unterschiedliche Werte berechnen. Die Tatsache, dass es sich um eine Abhängigkeit zwischen allen drei Werten handelt, liegt daran, dass Sie sie als mathematische Gleichung und nicht als programmatische Variablenzuweisung betrachten.

Ich schlage vor, Ihren Fall mit einer unveränderlichen Klasse IdealGas wie folgt zu implementieren

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

Lassen Sie mich die Implementierung erklären. Die Klasse IdealGas kapselte die 3 Eigenschaften Druck, Temperatur und spezifisches Volumen als Endwerte für die Unveränderlichkeit. Jedes Mal, wenn Sie getXXX aufrufen, wird keine Berechnung durchgeführt. Der Trick besteht darin, 3 Konstruktoren für alle 3 Kombinationen von 2 gegebenen Parametern zu haben. In jedem Konstruktor berechnen Sie die fehlende dritte Variable. Die Berechnung wird einmal zur Konstruktionszeit durchgeführt und dem dritten Attribut zugewiesen. Da dies im Konstruktor erfolgt, kann das dritte Attribut endgültig und unveränderlich sein. Für die Unveränderlichkeit gehe ich davon aus, dass die Klassen Druck, Temperatur und spezifisches Volumen ebenfalls unveränderlich sind.

Der einzige verbleibende Teil besteht darin, Getter für alle Attribute zu haben. Es sind keine Setter vorhanden, da Sie Parameter an Konstruktoren übergeben. Wenn Sie ein Attribut ändern müssen, erstellen Sie eine neue IdealGas-Instanz mit den gewünschten Parametern.

In meinem Beispiel sind die Klassen Pressure, Temperature und SpecificVolume einfache Wrapper mit einem doppelten Wert. Der Beispielcode ist in Java, kann aber verallgemeinert werden.

Dieser Ansatz kann verallgemeinert werden, alle zugehörigen Daten an einen Konstruktor übergeben, verwandte Daten im Konstruktor berechnen und nur Getter haben.

catta
quelle
2
Dies mit Unveränderlichkeit und Konstruktorinjektion zu lösen, ist hier die richtige Antwort. Im OO-Sprachgebrauch werden die drei verschiedenen Variationen der Gleichung in drei verschiedene Konstruktoren übersetzt. Jede Operation auf dem IdealGas erzeugt ein anderes Objekt (Wert). +1 Dies sollte hier die Antwort sein.
Greg Burghardt
+1 für die Bereitstellung einer Lösung
keuleJ
5

Das ist eine wirklich interessante Frage! Sie haben im Prinzip Recht, dass Sie Informationen duplizieren, da in allen drei Fällen dieselbe Gleichung verwendet wird, nur mit unterschiedlichen Unbekannten.

In einer typischen Mainstream-Programmiersprache gibt es jedoch keine integrierte Unterstützung für das Lösen von Gleichungen. Variablen in der Programmierung sind immer bekannte Größen (zum Zeitpunkt der Ausführung), so dass Ausdrücke in Programmiersprachen trotz der oberflächlichen Ähnlichkeiten nicht mit mathematischen Gleichungen vergleichbar sind.

In einer typischen Anwendung wird eine Gleichung, wie Sie sie beschreiben, nur als drei separate Ausdrücke geschrieben, und Sie leben mit der Duplizierung. Es gibt jedoch Gleichungslösungsbibliotheken, die in einem solchen Fall verwendet werden könnten.

Dies ist jedoch mehr als ein Muster, es ist ein ganzes Programmierparadigma, das als Lösen von Einschränkungen bezeichnet wird, und es gibt spezielle Programmiersprachen wie Prolog für diese Art von Problemen.

JacquesB
quelle
3

Dies ist eine häufige Irritation beim Codieren mathematischer Modelle in Software. Beide verwenden eine sehr ähnliche Notation für einfache Modelle wie das Gasgesetz, aber Programmiersprachen sind keine Mathematik, und viele Dinge, die Sie bei der Arbeit in der Mathematikwelt weglassen können, müssen explizit in Software ausgeführt werden.

Es wiederholt sich nicht. In den meisten Programmiersprachen gibt es kein Konzept für Gleichung, algebraische Transformation oder "Auflösen nach x". Ohne eine Software-Darstellung all dieser Konzepte gibt es keine Möglichkeit für Software, zu einer T = P * v / Rgegebenen Pv = RTGleichung zu gelangen .

Während es bei der Betrachtung einfacher Modelle wie dem Gasgesetz wie eine unangemessene Einschränkung der Programmiersprachen erscheint, erlaubt selbst eine etwas fortgeschrittenere Mathematik Gleichungen, die keine geschlossenen algebraischen Lösungen oder keine bekannte Methode haben, mit der die Lösung effizient abgeleitet werden kann. Für die Softwarearchitektur konnte man sich im komplexeren Fall nicht darauf verlassen.

Und der einfache Fall für sich? Ich bin mir nicht sicher, ob es sich überhaupt lohnt, die Spezifikation der Funktion in der Programmiersprache zu lesen. Modelle werden in der Regel ersetzt, nicht modifiziert und haben normalerweise nur wenige zu lösende Variablen.

Patrick
quelle
1

Die Klasse würde 3 Eigenschaften hat, natürlich Pressure, Temperatureund SpecificVolume.

Nein, zwei sind genug. Aber machen sie privat und setzen Methoden wie pressure(), temperature()und specificVolume(). Eine davon, die nicht den beiden privaten Eigenschaften entspricht, sollte die entsprechende Logik haben. So können wir die Datenverdoppelung beseitigen.

Es sollten drei Konstrukteuren mit Parametern sein (P, T), (P, v)und (T, v). Eine davon, die den beiden privaten Eigenschaften entspricht, fungiert einfach als Setter. Die anderen beiden sollten die entsprechende Logik haben. Natürlich wird die Logik dreimal geschrieben (zweimal hier und einmal im vorherigen Absatz), aber es handelt sich nicht um Duplikate. Auch wenn Sie sie als Duplikate betrachten, werden sie benötigt.

Drücken diese drei Ausdrücke dieselbe Beziehung aus?

Ja mathematisch und nein OO-ly. In OO-Objekten sind erstklassige Bürger keine Ausdrücke. Um erstklassige Bürger auszudrücken (oder um die Beziehung durch dasselbe auszudrücken), müssen wir beispielsweise eine Klasse schreiben, Expressionoder Equationdas ist keine leichte Aufgabe (es kann einige Bibliotheken geben).

Die Frage beschränkt sich nicht nur auf mathematische Beziehungen zwischen Daten.

Beziehung ist auch kein erstklassiger Bürger. Dafür müssen wir möglicherweise eine Klasse schreiben, Relationshipdie auch nicht einfach ist. Das Leben mit Duplikaten ist jedoch einfacher.

Wenn Sie sich für Duplikate entscheiden und die Parameter in der Beziehung hoch sind, sollten Sie das Builder- Muster verwenden. Auch wenn die Parameter weniger berücksichtigt werden, sollten Sie statische Faktoren verwenden , um Einschränkungen der Konstruktorüberladung zu vermeiden.

Durgadass S.
quelle
1

Sie können sich vorstellen, eine Klasse zu erstellen, die den Zustand eines idealen Gases darstellt.

Eine Klasse sollte das Verhalten eines idealen Gases darstellen. Eine Klasseninstanz - ein Objekt - repräsentiert den Zustand.

Dies ist keine Nissen pflücken. Das Klassendesign sollte aus Sicht des Verhaltens und der Funktionalität erfolgen. Das Verhalten wird öffentlich zugänglich gemacht, so dass ein instanziiertes Objekt (ja, das ist überflüssig) durch Ausübung von Verhalten einen Zustand erreicht.

Damit:

Gas.HeatToFarenheit( 451 );

Und das Folgende ist einfach Unsinn und in der realen Welt unmöglich. Und mach es auch nicht im Code:

Gas.MoleculeDistiance = 2.34;  //nanometers

Durch das Verhalten wird die Frage "Doppelte Informationen" in Frage gestellt

Das Beeinflussen derselben Statuseigenschaften durch verschiedene Methoden ist keine Duplizierung. Das Folgende wirkt sich auch auf die Temperatur aus, ist jedoch kein Duplikat der oben genannten:

Gas.PressurizeTo( 34 );  //pascals
Radarbob
quelle
0

Möglicherweise können Sie die Codegenerierung verwenden, um die verschiedenen Formen der Gleichung aus der von Ihnen angegebenen Einzelform zu generieren.

Ich kann mir vorstellen, dass es für komplexere Formeln ziemlich schwierig wäre. Aber für die einfache, die Sie geben, müssen Sie nur

  • Verschieben Sie Variablen durch Division von einer Seite zur anderen
  • Drehen Sie die linke und rechte Seite der Gleichung um

Ich kann mir vorstellen, dass Sie, wenn Sie dieser Liste Multiplikation, Addition und Subtraktion hinzufügen und Ihre Basisformel jede Variable nur einmal enthält, mithilfe der Zeichenfolgenmanipulation automatisch alle Versionen der Formel mit dem entsprechenden Boilerplate-Code generieren können, um die richtige Version unter Berücksichtigung der bekannten Variablen zu verwenden .

Wolfram Alpha verfügt beispielsweise über verschiedene Werkzeuge zur Manipulation von Gleichungen.

Jedoch!! Wenn Sie keine großen Listen dieser Klassen erstellen müssen, kann ich solchen Code nicht als effektive Nutzung Ihrer Zeit ansehen.

Sie müssen diesen Code nur einmal pro Funktion generieren, und Ihre Funktionen sind wahrscheinlich zu komplex, um so einfach wie Ihr Beispiel gelöst zu werden.

Das manuelle Codieren jeder Version ist wahrscheinlich die schnellste und zuverlässigste Methode zum Generieren der Funktionen. Obwohl Sie sich eine Lösung vorstellen können, bei der Sie codieren, um Werte iterativ zu erraten und die Wahrheit zu testen, müssen Sie die zu berechnenden Funktionen generieren und kompilieren die unbekannte Variable mit einer vernünftigen Menge an Rechenleistung.

Ewan
quelle
0

Ich denke, Sie überdenken dies. Betrachten Sie die folgende Lösung:

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

Sie müssen nur eine Funktion definieren, keine Duplizierung, Sie können diese sogar für eine interne Klassenimplementierung verwenden, oder Sie können diese Funktion einfach verwenden, indem Sie einfach ändern, welche Parameter Sie wo verwenden.

Sie können dasselbe Muster auch für jede Situation verwenden, in der Sie eine Funktion haben, bei der Werte vertauscht werden.

Wenn Sie eine statisch typisierte Sprache verwenden und sich mit einer Funktion befassen mussten, bei der Werte unterschiedliche Typen haben können, können Sie die Vorlagenprogrammierung verwenden (Hinweis: Verwenden der C ++ - Syntax)

template<class A, class B, class C, class D>
D computeIdealGasLaw(A a, B b, C c){
    return D(a * b / c);
}
whn
quelle