8

Summary: Is there a good design pattern to reduce duplication of information among tightly interdependant values?

In my line of work its fairly common to have a relationship among quantities such that you can derive one of the quantities if you know the others. An example could be the Ideal gas law:

Pv = RT

You could imagine creating a class to represent the state of an ideal gas. The class would have 3 properties, naturally Pressure, Temperature, and SpecificVolume each of an appropriate type.

For the user of an object of this class, it would seem natural to expect that if you set values for both Pressure and Temperature, you could then read out a value for SpecificVolume and expect the object to have calculated that for you.

Likewise, if you set values for both Pressure and SpecificVolume, you could then read out Temperature, etc.

To actually implement this class however requires some duplication of information. You would need to explicitly program all variations of the equation, treating a different variable as dependent in each case:

  1. T = P * v / R

  2. P = R * T / v

  3. v = R * T / P

which seems to violate the DRY principle. Though each expresses the same relationship these cases require independent coding & testing.

In real cases the logic I'm thinking of is more complex than this example, but exhibits the same basic problem. So there would be real value if I could express the logic only once, or at least fewer times.

Note that a class such as this would probably also have to deal with making sure it was properly initialized before values would be read out, but I think that is a secondary consideration.


Though I gave a mathematical example the question is not limited only to mathematical relationships among data. That just seemed to be a simple example to make the point.

  • 5
    Keep in mind that your example doesn't show any form of duplication. DRY only applies when you are actively recreating the same behavior. In your example case every variation of the equation solves for a different value - and thus, is a different behavior. The Pv = RT form of the equation is a useless generalization by itself from a OO viewpoint. – T. Sar Jul 25 '17 at 16:18
  • I don't see any duplication. – Goyo Jul 25 '17 at 17:09
  • 1
    There is also the issue of making "illegal" states impossible, i.e. those with Pv/T != R. Not sure if that idea could help you solving the underlying issues, or if that even complicates it. – Bernhard Hiller Jul 26 '17 at 7:50
  • As said T. Sar, there is no repetition here. Anything else that suggested Martin Maat is bound to provoke more trouble that it will solve. Basically your searching for engine that can automatically adapt a formula to compute the value missing from another, unless you find a library that does that (who knows ...) just give up on that, it's not worth the trouble. – Walfrat Jul 28 '17 at 7:35
  • As answers pointed out, Imperative languages have some issues. By Contrast, a Declarative/Logic Language like Prolog makes it trivial. Check Wikipedia. It can be done with an Imperative language, it "just" requires more work. – Armaghast Oct 4 '17 at 17:10
12

There is no issue from an OO perspective. You could have a static class GasLaw offering methods

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

et cetera

and there would not be a duplication issue. There is no object, no data members. Just behaviors that are all different.

You may regard the gas law as "one thing" but the operations are all distinct. There is nothing wrong with having a method for each of the law's use cases.

  • 3
    This doesn't really address the question of how internally in such a class to reduce duplication of information / logic. The external interface is not really a problem. – DaveInCaz Jul 25 '17 at 12:23
  • I disagree that it doesn't address the question. The answer is clear to me that Martin is saying don't do what you are asking how to do because there's clearly are far easier to understand way to accomplish the exact same thing. – Dunk Oct 4 '17 at 21:39
10

This isn't really possible in the way you ask.

Consider: I have an ideal gas object g. If I explicitly set all three of the temperature, pressure and specific volume, and then get the temperature again, like:

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

should it:

  1. give me the value t1 I originally set?
  2. calculate the value using the pressure and specific volume?
  3. calculate the value using pressure and specific volume only when I set those after the temperature?
  4. give me the original value if it is close enough to the calculated one (we should allow for rounding errors), and calculate as #2 or #3 otherwise?

If you must do this, you need to track a lot of state for each member: is it uninitialized, set explicitly by the client code, or caching a calculated value? Has the explicitly-set or cached value been invalidated by another member being set later on?

The behaviour is obviously hard to predict from reading the client code. This is a poor design, because it'll always be difficult to figure out why you got the answer you did. The complexity is a sign that this problem is a bad fit for OO, or at least that a stateful ideal gas object is a bad choice of abstraction.

7

First I don't think you violate the DRY Principle because all 3 formulas calculate different values. The fact it's a dependency between all 3 values is because you see it as a mathematical equation and not as an programmatic variable assignment.

I suggest to implement your case with an immutable class IdealGas as following

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

Let me explain the implementation. The class IdealGas encapsulated the 3 properties Pressure, Temperature and SpecificVolume as final values for immutability. Every time you call getXXX no computation will be done. The trick is to have 3 constructors for all 3 combinations of given 2 parameters. In every constructor you calculate the missing third variable. The computation is done one time, at construct time and assign to third attribute. Because this is done in constructor, the third attribute can be final and immutable. For immutability I assume that classes Pressure, Temperature and SpecificVolume are also immutable.

The only remaining part is to have getters for all attributes. No setters exists because you pass parameters to constructors. If you need to change an attribute, create a new IdealGas instance with desired parameters.

In my example, Pressure, Temperature and SpecificVolume classes are simple wrappers of a double value. The sample code is in java, but can be generalized.

This approach can be generalized, pass all related data into a constructor, compute related data in constructor and have only getters.

  • 2
    Solving this with immutability and constructor injection is the right answer here. In OO parlance, the three different variations of the equation translate to three different constructors. Each operation on the IdealGas produces another object (value). +1 This should be the answer here. – Greg Burghardt Oct 4 '17 at 12:32
5

This is a really interesting question! You are correct in principle that you are duplicating information because the same equation is used in all three cases, just with different unknowns.

But in a typical mainstream programming language there is no built-in support for solving equations. Variables in programming are always known quantities (at the time of execution), so despite the superficial similarities, expressions in programming languages are not comparable to mathematical equations.

In a typical application, an equation like you describe would just be written as three separate expressions and you live with the duplication. But equation solving libraries does exists and could be used in such a case.

This is more than a pattern though, it is a whole programming paradigm, called constraint solving, and there are dedicated programming languages like Prolog for this kind of problems.

3

This is a common irritation when encoding mathematical models into software. Both use a very similar notation for simple models like the gas law, but programming languages are not maths and many things you can leave out while working in the maths world have to be done explicitly in software.

It is not repeating yourself. There is no concept of equation, algebraic transformation or "solving for x" in most programming languages. Without a software representation of all those concepts there is no way for software to arrive at T = P * v / R given the Pv = RT equation.

While it might seem like an unreasonable limitation of programming languages when looking at simple models like the gas law, even just slightly more advanced maths allows equations that have no closed algebraic solutions, or no known method that can efficiently derive the solution. For software architecture you could not depend on it in the more complex case.

And the simple case by itself? I'm not sure that would even be worth having to read the specification of the feature in the programming language. Models tend to be replaced, not modified and usually only have a few variables to solve for.

1

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

No, any two is enough. But make them private and expose methods as pressure(), temperature(), and specificVolume(). One of them, not corresponding to the two private properties, should have the appropriate logic. Thus we can eliminate the data duplication.

There should be three constructors with parameters (P, T), (P, v) and (T, v). One of them, corresponding to the two private properties, simply acts as setters. Other two should have the appropriate logic. Of course logic is written thrice (twice here and once in previous paragraph), but they are not duplicates. Even if you consider them as duplicates they are needed.

Are these three expressions express the same relationship?

Yes mathematically and no OO-ly. In OO objects are first class citizens not expressions. To make expressions first class citizens (or to make the relationship expressed by the same thing) we need to write a class, say, Expression or Equation which is not an easy task (there may be some libraries).

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

Relationship is also not a first class citizen. For that we may need to write a class Relationship which is also not easy. But living with duplicates is easier.

If you decide to live with duplicates and if the parameters in the relationship are high consider using Builder pattern. Even if the parameters are less consider using Static Factories to avoid limitations of constructor overloading.

1

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

A class should represent the behavior of an ideal gas. A class instance - an object - represents the state.

This is not picking nits. Class design should be from a behavior and functionality perspective. The behavior is publicly exposed such that an instantiated object (yeah, that's superfluous) attains a state through exercising behavior.

So:

Gas.HeatToFarenheit( 451 );

And the following is simply nonsense and impossible in the real world. And don't do it in code either:

Gas.MoleculeDistiance = 2.34;  //nanometers

Behavior renders the "duplicate information" question moot

Affecting the same state properties through different methods is not duplication. The following affects temperature too, but it is not a duplicate of the above:

Gas.PressurizeTo( 34 );  //pascals
0

Possibly you could use code generation to generate the various forms of the equation from the single form you specify.

I can imaging it would be quite tricky for more complex formulae. But for the simple one you give you only have to

  • move variables from one side to the other by division
  • flip the left and right sides on the equation

I can imagine if you add multiplication, addition and subtraction to that list and your base formula has each variable only once then you could use string manipulation to automatically generate all versions of the formula with appropriate boilerplate code to use the correct one given the known variables.

Wolfram Alpha has various equation manipulation tools for example.

However!! unless you have large lists of these classes to produce I can't see such code as being effective use of your time.

You only need to generate this code once per function and your functions are likely to be too complex to be solved as simply as your example.

Manually coding each version is likely to be the quickest and most reliable method of generating the functions and although you can imagine a solution where you code to iteratively guess values and test for truth, I think you do need to generate and compile the functions to calculate the unknown variable with a sensible amount of processing power.

0

I think you are overthinking this, consider the following solution:

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

You only need to define one function, no duplication, you could even use this for an internal class implementation, or you could just use this function you just change which parameters you use where.

You can also use this same pattern for any situation where you have a function where values are swapped around.

If you are using a statically typed langauge and had to deal with a function where values could be different types, you could use template programming (note, using C++ syntax)

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.