开发者

Design patterns for dependent calculated properties in a class?

开发者 https://www.devze.com 2023-01-21 22:43 出处:网络
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 o

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 shou开发者_StackOverflowld I use to address the above problems?

Thanks


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 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.


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

I would avoid recalculating each time a setter is initialized because the object may not be in consistent state yet ( for example what will happen on a new object if you assign InputA before ever setting the rest?)

Trying to work out the dependencies between the properties may be pretty difficult and can quickly get out of hand leaving your object in inconsistent state. I would separate the input from the output and have explicit operation to calculate the state. For example let the object has read-only properties. Then have another object with the input data and pass it as parameter to "CalculateState" method. Optionally, depending on the specifics, make the object immutable and pass the input to the constructor. Having the calculation at single place with well defined input will help with the testing. The calculation of course can be decomposed, design patterns applied etc


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

精彩评论

暂无评论...
验证码 换一张
取 消