views:

74

answers:

4

I have a class representing a domain entity that contains many calculated properties. Most of the calculations depend upon other properties that are also calculated. In it's simplest form an example of the class could 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 is leaving me with the following problems:

  1. It is inefficient in that some of the calculations (some of which are lengthy) are getting repeatedly called.
  2. It is difficult to unit test individual calculations in isolation without providing all the required inputs for all the dependent calculations to work first.
  3. It is difficult to retrieve from persistence efficiently (I am using NHibernate) because even though the calculated data can be stored to the database, it doesn't get retrieved and is instead recalculated whenever the object is read.
  4. It is difficult to add calculations as the unit tests grow larger and larger with the required inputs.

I have experimented with using a calculator object and the strategy pattern to set internal fields for the properties but I am ending up with an overly long controlling function to force the calculations to occur. Also moving all the calculations out to another object turns the original entity into an anaemic domain object that I keep reading should be avoided.

What design patterns and class structure should I use to address the above problems?

Thanks

+1  A: 

Do the work on the write path, not the read path. Then you can populate the latest cached values from a persistence layer without worrying.

So when value A gets written, all calculations that depend on A are redone.

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

James Branigan
Thanks James, I can see how that would work.
Forever Refactoring
A: 

James makes a good point, essentially use pre-calculating to minimize the inefficiencies during retrieval time.

Of course this requires you to know the dependent properties when you change a value in your class. I would look into implementing the INotifyPropertyChanged or IObservable<T> interface for those properties that others depend on, this would be the Observer/Observable or Publish/Subscribe design pattern.

BrokenGlass
Thanks for the patterns, I'll definitely give the IObservable<T> a go.
Forever Refactoring
A: 

To avoid repeated calculations you can, follow the pattern of calculating any value if the related variables changed, it can be achieved by 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; 
        }
    }
}
ArceBrito