在类中依赖计算属性的设计模式?

时间:2010-10-26 23:18:07

标签: c# nhibernate design-patterns

我有一个表示包含许多计算属性的域实体的类​​。大多数计算依赖于也计算的其他属性。在它最简单的形式中,类的示例可能看起来像这样。

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

然而,这个解决方案给我留下了以下问题:

  1. 一些计算(其中一些是冗长的)被反复调用是低效的。
  2. 很难单独对单个计算进行单独测试,而无需为所有相关计算提供所有必需的输入。
  3. 很难有效地从持久性中检索(我正在使用NHibernate),因为即使计算出的数据可以存储到数据库中,也不会检索它,而是在读取对象时重新计算。
  4. 随着单位测试随着所需输入变得越来越大,很难添加计算。
  5. 我已经尝试过使用计算器对象和策略模式来设置属性的内部字段,但我最终会得到一个过长的控制函数来强制进行计算。同时将所有计算移动到另一个对象会将原始实体变为贫血域对象,我应该避免阅读。

    我应该使用哪些设计模式和类结构来解决上述问题?

    由于

4 个答案:

答案 0 :(得分:5)

在写入路径上执行工作,而不是读取路径。然后,您可以从持久层填充最新的缓存值,而无需担心。

因此,当写入值A时,所有依赖于A的计算都将重做。

这种方案在读取次数大于写入次数的情况下效果很好。

答案 1 :(得分:1)

缓存是一个好主意 - 如果它可能有很多缓存命中率。

每次初始化setter时都会避免重新计算,因为对象可能还没有处于一致状态(例如,如果在设置其余部分之前分配InputA,新对象会发生什么?)

尝试计算属性之间的依赖关系可能非常困难,并且可能很快失控,使对象处于不一致状态。我将输出与输出分开并具有显式操作来计算状态。  例如,让对象具有只读属性。然后使用输入数据生成另一个对象,并将其作为参数传递给“CalculateState”方法。 (可选)根据具体情况,使对象不可变并将输入传递给构造函数。在具有明确定义的输入的单个位置进行计算将有助于测试。课程计算可以分解,设计模式应用等。

答案 2 :(得分:0)

James提出了一个很好的观点,主要是使用预先计算来最小化检索时间内的低效率。

当然,这需要您在更改类中的值时了解相关属性。我会考虑为其他人所依赖的属性实现INotifyPropertyChangedIObservable<T>接口,这将是Observer/Observable或发布/订阅设计模式。

答案 3 :(得分:0)

为避免重复计算,您可以按照计算任何值的模式,如果相关变量发生变化,可以通过使用状态变量来实现:

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