How can I make the Strategy Pattern maintainable without compromising it?

Viewed 68

I am struggling with a design that meets all my requirements

I have a set of algorithms that can be performed individually, or combined in various configurations to produce additional outputs.

  1. These algorithms can be costly, and if they are run once, the result should be cashed fo later use.

  2. The Algorithms can be used in different contexts.

  3. The algorithms should be open for extension so they are able to keep up with yearly standards revisions.

  4. The design should be maintainable

My first attempt to satisfy these was to use the Strategy pattern with the following design…

enter image description here

The output from the behaviours calculations are cached in the behaviours themselves.

This satisfies requirements 1,2,3, but not 4.

The call to CalculateOutput() requires 30 different parameters, it’s own, and those necessary to allow it to call the other concrete behaviours. A change to the parameters for one function, will have a knock on effect, and often results in all behaviours having to update their parameter list.

Furthermore, the caching system did not work correctly as there was not always a 1:1 mapping between behaviours. For example, to produce an output from the EnergyBehaviour, it needs to perform multiple calls NHSolarPosition. How can “NHSolarPosition” cache multiple “angleOfIncidence_”s for only a single cached “outPut_”?

I tried to address the above issue, by encapsulating all parameters in their own type “PVPanel”, and passing this to behaviours. This was problematic for two reasons. Firstly, there was a significant overhead in calls to simple behaviours, as only a couple of parameters might be required, however there were many other parameters that were not being used, or relevant. Secondly, passing a “PVPanel” type into the behaviours caused requirement 2 to fail, I was not able to use the behaviours in a context other than in relation to PVPanels.

I am struggling to reconcile all my requirements into a single, elegant design.

1 Answers

I'm not sure if your behaviour classes represent a function and its parameters, and if you're using one behaviour instance and updating its attributes for different invocations. You could still cache multiple invocations in your behaviours but it might be simpler to take that caching out of here and instead make it a responsibility for a user of these systems.

I should add that you would only want to cache if you would always get the same result for an output calculation, given the state of the system. Could you get a different result for the same output from one of these behaviours given a different state in the model?

A comment on your question suggests a std::map of parameters. Another option is to have specific parameter classes for the calculations. Look at the visitor pattern or std::variant for how to wrap up these specific parameter types.

std::map might be easier, as it could solve your issue with PVPanel and requirement 2. It would just be a case of populating the std::map from a PVPanel instance or any other type. Alternatively, you could take the accessors on your PVPanel type and just move them up into an interface that PVPanel and other future types implement.

Related