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.
1 Answer 1
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
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
, andCircle
andSquare
are derived from it.- It makes no sense to pollute
Shape
withCircle
orSquare
specific data. - It also makes no sense to pollute
Circle
andSquare
with higher level concerns -- such as how does the user gather the data to construct them.
- It makes no sense to pollute
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.
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.
-
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– Amir05/31/2019 06:32:20Commented 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– R Sahu05/31/2019 07:07:50Commented 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– Amir05/31/2019 08:46:51Commented 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– R Sahu05/31/2019 19:35:56Commented May 31, 2019 at 19:35
Explore related questions
See similar questions with these tags.
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.