I'm developing some complex calculations that require the 'steps' to be determined (strategy pattern type implementation) and I'm not sure the best way to inject the step subcalculation classes into the main class.
I've looked into policy-based design but read the policy design is for 'compile time polymorphism' not runtime. Plus, I wasn't sure how to use templates as some of the subcalculation classes need constructor parameters.
I've started implementing virtual 'interface' classes for each step and injecting each step as a unique_ptr in the constructor but wasn't sure if this the correct 'modern C++' way.
I initially started implementing all the functionality in the main class but found this made it difficult if not impossible to unit test each step independently.
The structure is similar to below:
class CalculationStepA
{
public:
// default constructor
StepAResult performStep(const input& requiredInput);
};
class CalculationStepBType1
{
public:
// default constructor
StepBResult performStepB(const stepBInput& requiredInput);
};
class CalculationStepBType2
{
public:
CalculationStepBType2(const inputIOnlyNeedForType2& parameters)
{
// initialize class members from input
// need for this calculation type
}
StepBResult performStepB(const stepBInput& requiredInput);
};
class CalculationStepCType1
{
public:
CalculationStepBType2(const inputIOnlyNeedForType1& parameters)
{
// initialize class members from input
// need for this calculation type
}
StepCResult performStepC(const stepCInput& requiredInput);
};
class CalculationStepCType2
{
public:
CalculationStepBType2(const inputIOnlyNeedForType2& parameters)
{
// initialize class members from input
// need for this calculation type
}
StepCResult performStepB(const stepCInput& requiredInput);
};
class ClassThatUsesAllTheCalculations
{
public:
ClassThatUsesAllTheCalculations(/* take required parameters that determine which step types I need */)
{}
// possible constructor?
ClassThatUsesAllTheCalculations(
std::unique_ptr<IStepACalculationStrategy> stepA,
std::unique_ptr<IStepBCalculationStrategy> stepB,
std::unique_ptr<IStepCCalculationStrategy> stepC)
{
}
FinalResult executeCalculation(const finalInputRequiredHere& input)
{
auto stepAresult = stepACalculator(somethingFromInput);
// logic to use stepA and determine if we should continue
auto stepBresult = stepBCalculator(somethingFromStepAResult);
// again, logic to use stepB and determine if we should continue
auto stepCresult = stepCCalculator(somethingFromStepBResult);
// assemble final result
return theFinalResult
}
// other method needed to setup calculation
private:
TypeForStepACalculation stepACalculator;
TypeForStepBCalculation stepBCalculator;
TypeForStepCCalculation stepCCalculator;
};
Any help on determining the best design would be great appreciated.
Whats about a simple inheritance?
struct StepA{
virtual StepAResult perform(StepAParams)=0;
};
struct someStepAImpl : public StepA{
virtual StepAResult perform(StepAParams params) override {
//actual implementation
}
};
Whether your calculation class then uses a reference (hast to be set on construction), a std::reference_wrapper
(not null, but can be changed later) or some kind of (smart) pointer (might be nullptr
, don't forget to check for that, but easiest when it comes to managing lifetime) depends on how you plan to use it and how you want to manage the lifetime of the objects. An example using unique_ptr
as you did would be:
class Calculator
{
public:
Calculator(
std::unique_ptr<StepA> stepA,
std::unique_ptr<StepB> stepB,
std::unique_ptr<StepC> stepC
)
:m_stepA(std::move(stepA)),m_stepB(std::move(stepB)),m_stepC(std::move(stepC))
{}
FinalResult executeCalculation(const finalInputRequiredHere& input)
{
//logic
auto stepAresult = stepA->perform(StepAParams);
//logic
auto stepBresult = stepB->perform(StepBParams);
//logic
auto stepCresult = stepC->perform(StepAParams);
//logic
return FinalResult();
}
private:
std::unique_ptr<StepA> m_stepA=nullptr;
std::unique_ptr<StepB> m_stepB=nullptr;
std::unique_ptr<StepC> m_stepC=nullptr;
};
void somewhereElse(){
std::unique_ptr<StepA> stepa(new someStepAImpl());
std::unique_ptr<StepB> stepa(new someStepBImpl());
std::unique_ptr<StepC> stepa(new someStepCImpl());
Calculator calc(
std::move(stepa),
std::move(stepb),
std::move(stepc)
);
calc.executeCalculation(...);
}