Design models of dependent computed properties in a class?

I have a class representing a domain object that contains many calculated properties. Most calculations depend on other properties, which are also calculated. In its simplest form, an example class might look something like this.

public class AnalysisEntity { public decimal InputA { get; set; } public decimal InputB { get; set; } public decimal InputC { get; set; } public decimal CalculatedValueA { get { return InputA * InputC; } } public decimal CalculatedValueB { get { decimal factor = FactorGenerator.ExpensiveOperation(); return CalculatedValueA / factor; } } public decimal CalculatedValueC { get { return InputA * InputB; } } public decimal CalculatedValueD { get { return (CalculatedValueA * InputB) / CalculatedValueB; } } public decimal CalculatedValueE { get { return CalculatedValueD / aConstant; } } } 

However, this solution leaves me with the following problems:

  • This is inefficient because some calculations (some of which are long) are called repeatedly.
  • It is difficult to isolate unit tests of individual calculations without providing all the necessary inputs for all dependent calculations to work in the first place.
  • It is difficult to obtain from persistence efficiently (I use NHibernate) because, although the calculated data can be stored in the database, it does not receive the extraction and is instead recounted whenever the object is read.
  • It is difficult to add calculations, as unit tests grow more and more with the required inputs.

I experimented using a calculator object and a strategy template to set internal fields for properties, but in the end I have a control function too long to make the calculations happen. In addition, moving all calculations to another object turns the original object into an anemic domain object, which I continue to read, should be avoided.

What design patterns and class structure should be used to solve the above problems?

thanks

+4
source share
4 answers

Do the work on the write path, not the read path. Then you can populate the last cached values ​​from the save level without worrying.

So, when the value of A is written, all calculations that depend on A are redone.

This scheme works great where the number of reads is greater than the number of records.

+5
source

Caching is a good idea - if it is likely to have many cache hits.

I would avoid recounting every time the setter is initialized, because the object cannot yet be in a state of consistency (for example, what happens to the new object if you assign InputA earlier than you ever set the others?)

Trying to develop dependencies between properties can be quite complex and can quickly get out of hand, leaving your object in an inconsistent state. I would separate the input from the output and have an explicit operation to calculate the state. For example, let an object have read-only properties. Then add another object with input data and pass it as a parameter to the "CalculateState" method. Optionally, depending on the specifics, make the object immutable and pass the input to the constructor. Conducting testing in one place with a clearly defined input will help in testing. Course calculation can be expanded, design patterns applied, etc.

+1
source

James makes a good point, in fact, uses precalculation to minimize inefficiencies during the search.

Of course, this requires that you know the dependent properties when you change the value in your class. I would like to study the INotifyPropertyChanged or IObservable<T> interface for those properties that others depend on, it will be an Observer / Observable or Publish / Subscribe design template.

0
source

To avoid repeated calculations, you can follow the calculation template of any value, if the associated variables have changed, it can be achieved using state variables:

 public class AnalysisEntity { private decimal _ca; private decimal _cb; private decimal _cc; private bool calculate_a = false; private bool calculate_b = false; private bool calculate_c = false; private bool calculate_d = false; private bool calculate_e = false; public decimal InputA { get { return a;} set { a=value; calculate_a = true; calculate_c = true; } } public decimal InputB { get { return b;} set { b=value; calculate_c = true; calculate_d = true; } } public decimal InputC { get { return c;} set { c=value; calculate_a = true; } } public decimal CalculatedValueA { get { if( calculate_a ) { _ca = InputA * InputC; calculate_a = false; calculate_b = true; } return _ca; } } public decimal CalculatedValueB { get { if( calculate_b ) { _cb = (CalculatedValueA / FactorGenerator.ExpensiveOperation()); calculate_b = false; calculate_d = true; } return _cb; } } public decimal CalculatedValueC { get { if( calculate_c ) { _cc = InputA * InputB; calculate_c = false; } return _cc; } } public decimal CalculatedValueD { get { if( calculate_d ) { _cd = (CalculatedValueA * InputB) / CalculatedValueB; calculate_d = false; calculate_e = true; } return _cd; } } public decimal CalculatedValueE { get { if( calculate_e ) { _ce = CalculatedValueD / aConstant; calculate_e = false; } return _ce; } } } 
0
source

All Articles