Search code examples
c++dependency-injectionstrategy-pattern

C++ injecting 'strategies' through constructor


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.


Solution

  • 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(...);
    }