如何重构/扩展以下模型

时间:2010-08-13 14:59:58

标签: c# design-patterns oop caching refactoring

我目前有一个缓存实现(使用数组)用于在模拟期间执行的繁重计算。缓存的结构如下:

alt text

它的工作方式:

CalculationsAbstract calculationsCache = new CalculationsCache();

// Constructor of CalculationsCache
public CalculationsCache()
{
   this.Proxy = new Calculations();
   Proxy.Proxy = this;
}

calculationsCache.CalculateValue1();    
// Checks "Value1" array for existing value, if not, the actual computation is called
// via Proxy object, value retrieved is cached in array then returned to user.

现在,我正在尝试添加特定于某个方案的新计算,并且不适合将它们放在CalculationsAbstractCalculationsCalculationsCache中但是,ScenarioA仍会使用旧类中的现有计算。

我正在尝试在名为ScenarioACalculationsScenarioACalculationsCache的新类中添加新计算及其数组,就像它为Value1,Value2,...等做的那样但是我很困惑至于这些新类如何适合现有模型。

这就是我试图做的事情:

internal interface IScenarioACalculations 
{
    float GetScenarioAValue5();
}

ScenarioACalculations : Calculations, IScenarioACalculations 
ScenarioACalculationsCache : CalculationsCache, IScenarioACalculations 

鉴于在整个项目中,我只保留对CalculationsAbstract类型的引用(如上面的代码示例所示),并且我无法将IScenarioACalculations对象强制转换为CalculationsAbstract,什么是将来添加ScenarioA计算和可能的ScenarioB等的最佳方法?

很抱歉这么长。

谢谢。

3 个答案:

答案 0 :(得分:3)

基本上,你在CalculationsCache中有一个装饰器。由于在Calculations对象周围编写这样的薄层将是一个维护问题,因此您应该在Calculations对象本身内部进行缓存。

试试这个:

Scenario
{
  IDictionary<int, float> cache = new Dictionary<int, float>;

  void Reset()
  {
    cache.Clear();
  }
}

PhysicsScenario : Scenario
{
  const int AccelerationType = 1; // or string, or whatever. Just needs to be a key into the cache.
  float[] CalculateAcceleration() 
  {
     if (cache.Keys.Contains(AccelerationType))
     {
       return cache[AccelerationType];
     }
     else
     {
       float[] values = ActualCalculateAcceleration();
       cache.Add(AccelerationType, values);
       return values;
     }
  }
  float[] CalculateVelocity() {...} 
  // More heavy calculations
}

ChemistryScenario : Scenario
{
  float[] CalculateVolume() {...}
  float[] CalculateCalorificValue() {...}
  // More heavy calculations
}

您不会谈论缓存对象的生命周期管理。如果它与您的Calculations对象完全相同,您可以使用它。

如果还有其他内容,您可以使用Inversion of Control原则从容器中传递缓存Dictionary。

答案 1 :(得分:1)

似乎您描述此模型的方式是CalculationsAbstract中的两个方法始终被覆盖。为什么不将它设为接口ICalculationsAbstract并具有

internal interface IScenarioACalculations : ICalculationsAbstract 
{
    float GetScenarioAValue5();
}

你仍然可以:

ScenarioACalculations : Calculations, IScenarioACalculations
ScenarioACalculationsCache : CalculationsCache, IScenarioACalculations 

但现在您的IScenarioACalculations可以投放到ICalculationsAbstract

答案 2 :(得分:1)

我仍然看到总是必须修改所有内容以添加更多计算的问题。也许您的ICalculations应该有Calculate()IScenario方法。 IScenario将提供执行场景和返回的方法,检查相等性的方法,获取哈希码,以及可能的克隆方法。

我们的想法是,您要添加的每个新计算都是一个单独的类。它们每个都可以创建并传递给ICalculations,然后可以决定如何处理它。很可能实现会针对某种缓存检查IScenario's哈希码,如果找到则返回该值,如果没有调用IScenario's Execute()存储带有IScenario's哈希码的值进行缓存然后返回

所以它基本上是Command Pattern。我看到你获得的是ICalculations实现不必经常改变(假设我理解你在做什么)并且有一个简单的扩展点。您稍后还可以添加某种运行上下文的想法,并使场景组合允许聚合的分布式计算等内容,但很少需要更改类/接口的用户,同时您可以收集这些复杂性。需要它们的课程。

我看到的缺点是你需要知道为每个场景覆盖GetHashCode()Equals()方法。如果你知道你只关心总是代表计算参数的公共属性,那么可能会以通用方式(包括Clone())使用反射来完成它。我也知道ReSharper可以为你生成这些方法。

如果您只担心3次计算没有改变,这会变得过于先进,但如果您不得不在不同复杂程度的不同时间添加/维护几次,那么该方法可能具有优点。

以一些基本代码为例:

interface ICalculations {
    double Calculate(IScenario scenario);
}

interface IScenario{
    double Execute();
    IScenario Clone();
}

public class CalculationsCacher: ICalculations {
    IDictionary<IScenario, double> _cache;

    public CalculationsCacher(IDictionary<IScenario, double> existingCache = new Dictionary<IScenario, double>()){
        //c#4 has optional parameters
        _cache = existingCache
    }

    public double Calculate(IScenario scenario){
        if(_cache.ContainsKey[scenario]) return _cache[scenario];

        _cache[scenario.Clone()] = scenario.Execute();
        return _cache[scenario];
    }
}

class AccelerationScenario: IScenario{
    // properties
    public AccelerationScenario(double distance, TimeSpan time){
       // set things
    }

    public double Execute(){
      //calculate
    }

    public IScenario Clone(){
      //use contructor to build a new one
    } 

    public override int GetHashCode(){
      //generate the hashcode
    }

    public override bool Equals(object obj){
      //you should also override this when you override GetHashCode()
    }
}

//somewhere in your app
var scenario = new AccerlationScenario(/* values */);
return calculations.Calculate(scenario);

我添加了克隆方法,以防属性可写,您不想修改缓存方案的参数并取消验证存储的值。