0

I'm designing an API and I ended up having a few pure abstract classes. Because of the nature of the problem that I'm solving, each derived class has to be initialized with different sets of parameters. My solution was to basically define their constructors and pass the appropriate parameters when I'm making each class using the factory method. This makes the factory method rather complicated. Since I have to pass all possible parameters and then filter the appropriate parameters for each derived class and pass it around. It also opens up a path for error.

In order to avoid this complication, I would like to have the parameters as a type. So, I'm thinking to define struct BaseClassParams and basically include everything that derived classes need into it and simply pass the param to their constructor. This achieve what I want to do but since parameters of each derived class might differ from one to another, I want a better encapsulation. I thought doing something this:

class Base {

public:
     struct BaseClassParams {
          struct DerivedClass_A_Params {

          };
          struct DerivedClass_B_Params {

          };
     };
};

This way, I can encapsulate everything more neatly and in the factory method, only pass the appropriate parameter section to the derived class constructor. I wonder if you have faced this issue and if you have any suggestions? Overall what do you think of this approach and problem.

Edit: To clarify a bit, these derived classes are going to be used by a class, i.e., Manager. The Manager can choose to create different derived class, let's call them Employee, with different parameters. The Manager class can be initialized using a config file, i.e., JSON, or directly. If the Manager is created directly via the API, then Manager::Builder class can choose to create as many different employees with different parameters. So, I need to create these objects at run time.

P.S. I found this question and answer. The difference here is that my derived classes are not necessarily sharing parameters, they might have completely different parameters. This is kind of relevant too.

7
  • 2
    What is the reason you are using a factory here? The client will have to know which set of parameters to prepare, so they might just as well create the right class directly. Commented May 23, 2019 at 11:55
  • @BartvanIngenSchenau I'm using a factory because I need to dynamically create different derived objects, often with random parameters, or from a config file.
    – Amir
    Commented May 23, 2019 at 12:01
  • 4
    So ... how do you randomly choose the correct parameters for your random derived type? If this is all coming from a config file, you should just let each class's concrete factory read the parameters it needs directly instead of having a strongly-typed intermediate object (ie, map<string,string> or Boost.PropertyTree)
    – Useless
    Commented May 23, 2019 at 12:06
  • @Useless regarding the first question, each derived class has a method that knows how to randomize its parameters, after construction, the user can ask for randomization, or it can set it and I check if the values are correct. I actually first went with a JSON object to pass everything around but noticed that people tend to make a lot of mistakes. So, I'm trying to define these intermediate types and read everything one to my types. Basically I think I'm converging to the Boost.PropertyTree but now I'm wondering if I should define these intermediates or not. I hope I answered your question.
    – Amir
    Commented May 23, 2019 at 12:20
  • 1
    But your derived classes can have different params, so you either need DerivedClassParams, and now you need ... an abstract factory for the params you pass to your other abstract factory? This just splits the configuration part of a factory into two steps (parse+populate param object, then use param object) and I don't see any benefit in that. Or your param object is the union of all derived type params, and then it doesn't help at all with communicating which ones are actually used for a given derived type.
    – Useless
    Commented May 23, 2019 at 14:31

1 Answer 1

2

Problem with BaseClassParams

Using

 struct BaseClassParams {
      struct DerivedClass_A_Params {
      };

      struct DerivedClass_B_Params {
      };
 };

to capture the data needed to construct derived classes is DOA in my book. As soon as you need DerivedClass_C, you'll have to go back to the base class and update it to

 struct BaseClassParams {
      struct DerivedClass_A_Params {
      };

      struct DerivedClass_B_Params {
      };

      struct DerivedClass_C_Params {
      };
 };

That violates The Open-Closed Principle and should be avoided.

Separation of concerns

  1. Make sure that Base and its derived classes are clean. Don't pollute them with what mechanisms are used to construct them.

    Say your base class is Shape, and Circle and Square are derived from it.

    • It makes no sense to pollute Shape with Circle or Square specific data.
    • It also makes no sense to pollute Circle and Square with higher level concerns -- such as how does the user gather the data to construct them.
  2. Using a factory to construct any derived type of a given base type implies the ability to capture derived class specific data in a derived class agnostic manner. You'll have to find a generic way of capturing such data. The can be:

    • std::map<std::string, std::string>,
    • std::vector<std::string>,
    • A JSON object, etc.
  3. You will need a set of classes/functions to bridge the gap between (1) and (2) above. That's were you need concrete factories that are able to:

    • Take the generic data from (2)
    • Validate the data for constructing the specific object type they are responsible for constructing.
    • Construct objects of the derived type.

My suggestion in pictorial form

The image below illustrates my suggestion.

enter image description here

4
  • Thanks a lot for your answer, I’ve already implemented it before your comment with the help of @Useless comment. Basically, I did exactly what you said, I didn’t add a type to the Base and define specific types for each derived. Then, the factory of the Base handles a JSON object, parse it to appropriate Derived::Parameters and pass it to it’s constructor. I think I’m happy with the outcome. It both allows me to have a generic factory, and have different types for each subs class to manipulate and pass around.
    – Amir
    Commented May 31, 2019 at 6:32
  • Then, the factory of the Base handles a JSON object, parse it to appropriate Derived::Parameters and pass it to it’s constructor. That should concern you. If I understand that correctly, it still vioates the Open-Closes Principle.
    – R Sahu
    Commented May 31, 2019 at 7:07
  • I’m trying to understand what’s violating the open-close principle. It’s the Derived::Parameters, right? Because client couldn’t just inherit from the base class and start working with it, he also has to know that there is another type, Parameters, that he has to create to make things uniform, correct? So, if I don’t implement the Parameters types, then I’m good, basically I only use the constructor without specific types and anyone who derives from the base, just do the same and can expand the class without missing something. Do I see this right?
    – Amir
    Commented May 31, 2019 at 8:46
  • It’s the Derived::Parameters, right?. Right. If constructor class hierarchy matches the object class hierarchy, then you should be good.
    – R Sahu
    Commented May 31, 2019 at 19:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.