views:

83

answers:

3

I currently have a cache implementation (using arrays) for the heavy computations performed during the simulation. The structure of the cache is as follows:

alt text

The way it works:

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.

Now, I'm trying to add new computations which are specific to a certain scenario, and wouldn't be appropriate for them to be placed in CalculationsAbstract, Calculations and CalculationsCache, however ScenarioA would still use existing calculations in the old classes.

I'm trying to add the new calculations and their arrays in new classes named ScenarioACalculations and ScenarioACalculationsCache, just like it was done for Value1, Value2,...etc but I'm confused as to how these new classes would fit into the existing model.

This is what I tried to do:

internal interface IScenarioACalculations 
{
    float GetScenarioAValue5();
}

ScenarioACalculations : Calculations, IScenarioACalculations 
ScenarioACalculationsCache : CalculationsCache, IScenarioACalculations 

Given that throughout my project, I only hold a reference to type CalculationsAbstract (as in the code example above), and I can't cast my IScenarioACalculations object to CalculationsAbstract, what would be the best way to add ScenarioA calculations and possibly ScenarioB...etc in the future ?

Sorry for making this long.

Thank you.

+3  A: 

Basically, you have a Decorator in CalculationsCache. Since writing such a thin layer around your Calculations object is going to be a maintenance issue, you should bring the caching inside the Calculations object itself.

Try this:

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
}

You don't talk about your lifecycle management for your cache object. If it is exactly the same as your Calculations object, you can probably use this.

If there is something else going on, you can pass the cache Dictionary in from the container using Inversion of Control principles.

somori
+1  A: 

It seems like the way you described this model that the two methods in CalculationsAbstract are always overridden. Why not make it an interface ICalculationsAbstract and have

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

You can still have:

ScenarioACalculations : Calculations, IScenarioACalculations
ScenarioACalculationsCache : CalculationsCache, IScenarioACalculations 

But now your IScenarioACalculations can be cast into an ICalculationsAbstract

Jack
+1  A: 

I still see the issue of always having to modify everything to add more calculations. Perhaps your ICalculations should have a single Calculate() method that took in an IScenario. The IScenario would provide a method to execute the scenario and return, a method to check equality, get the hashcode, and perhaps a method to clone.

The idea is that each new calculation you want to add is a separate class. They could each be created and passed to an ICalculations which could then decide what to do with it. Most likely the implementation would check the IScenario's hashcode against a cache of some sort, if found return the value, if not call the IScenario's Execute() store the value with the IScenario's hashcode for caching then return.

So it would basically be the Command Pattern. What I see you gain is that the ICalculations implementation won't have to change very often (assuming I understand what you are doing) and there is an easy point of extension. You could also later add in ideas of some kind of run context and make scenarios composites to allow things like distributed calculations that get aggregated but to the user of the classes/interfaces very little would have to change while you can tuck away those complexities in the classes that need them.

The disadvantages I see is that you need to know to override the GetHashCode() and Equals() method for every scenario. Possibly able to do it in a generic way (this includes the Clone()) using reflection if you know you only care about public properties that will always represent the parameters for the calculation for example. Also I know ReSharper can generate those methods for you.

This would be getting too advanced if you were only worried about having 3 calculations that didn't change, but if you were having to add/maintain several at different times of different complexities then the approach probably has merit.

Just some basic code as an example:

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

I added the clone method in case the properties were writable you don't want to modify the parameters for a cached scenario and unvalidate the stored value.

Sean Copenhaver