Design Patterns – Abstract Base Class Decides Child Class at Runtime

abstract classcdesign-patternsobject-orientedsingle-responsibility

I have an Abstract Base Class AbstractModel

class AbstractModel {
 public:
  struct predictionStructure{};
  virtual predictionStructure predict(CompanyLib::Matrix<double> data) = 0;
  std::string modelType;
 public:
  //constants
  const uint32_t IMPORTER_VERSION = 1;
 protected:
  Preprocessor dataProcessor;//object calls func's to transform data. Children need capability, made into obj for SRP?
};

and child classes ABCModel

class ABCModel: AbstractModel {
 public:
  struct ABCPredictionStruct : AbstractModel::predictionStructure {
    CompanyLib::Matrix<double> scores;
    std::vector<double> q;
    std::vector<double> tsqs;
  };
  ABCModel(std::ifstream & modelFile);
  virtual predictionStructure predict(CompanyLib::Matrix<double> data) override;
 private:
  uint32_t varCount;
  std::vector<double> otherData;
};

and XYZModel

class XYZModel: AbstractModel {
 public:
  struct XYZPredictionStruct : AbstractModel::predictionStructure {
    CompanyLib::Matrix<double> y;
  };
  XYZModel(std::ifstream & modelFile);
  virtual predictionStructure predict(CompanyLib::Matrix<double> data) override;
 private:
  CompanyLib::Matrix<double> means;
};

The problem is, I need to read the first ~64 bytes of an input file to decide which type of child class to construct, as it is a flag in the input file. While I could do this with a static function in AbstractModel, I am trying to minimize the amount of concrete data/methods in the base class (Sr. Eng said to make it Pure Virtual Class but I need to require children to construct/use Preprocessor).

What is the best way to go about this? static protected function in AbstractModel? Make a ModelCreator class that handles file read & creation, and make AbstractModel more interface-like (called factory design pattern I think?).

I struggle a lot with designing a project structure, and this feels messy considering the Sr. Engineer said I should make AbstractModel a Pure Virtual Class/interface, and I have non-virtual things in it. If anyone can point out other mistakes I'm making in terms of best practices?

Best Answer

You need to read the first 64 bytes which model you need to create.

Then, based on some criteria you chose the model to use:

  • Option 1: just implement this in a reading function, with a simple condition to construct the right model. Super easy, super simple.

  • Option 2: since the model is abstract, there is an aim to allow easy extension of the models. The issue then, is that you’d have to modify the reading function. An alternative is then to implement a factory function, that takes a parameter to decide which model to instantiate.

  • Option 3: like option 2, but you find a clever trick to let each model register to the factory along with a parameter or a condition function.

The factory should not be a static function of the abstract class: this would break the Open/Closed principle, since for every new cild class, you’d need to modify the parent class. Moreover, one could argue that this also breaks the single responsibility principle, since there would be more than one reason to change.

A separate factory allows you to keep things as independent as needed: abstract doesn’t need to know all its children, and the factory only needs to know the children needed for your current project )”(unless you use option 3 and keep the factory very general and reusable across several projects.