Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with constructors of differing length with the Factory design pattern?

Tags:

c++

factory

I want to have a class that creates different sorts of objects based on a string I pass it. From my research, this best describes the Factory design pattern. I am having success implementing it, but I've run into a design issue: I don't know how to create objects with different length constructors.

Let's take for example, an abstract parent class called Pet. From it are 3 children: Fish, Cat, and Dog. They all inherit weight and color from Pet, so that goes in their constructors. But a fish might want a number of fins and a boolean regarding whether it is a saltwater fish. That's a 4 parameter constructor. Cat would like number of legs. That's 3 parameters. The dog might have parameters for legs, breed, and whether he plays well with other dogs, for 5 parameters.

In C++, I understand that there isn't any reflection, so the most common practice seems to be to just declare a map of string to function pointers, where the function pointer points to a function that looks something like this:

    template<typename T> Pet* createObject(int weight, std::string color) {return new T(weight, color);}

Again, I'm not sure how I would stuff more parameters into the call without affecting the calls of other objects' constructors.

I can think of two workarounds: make new functions to accept different amount of parameters or make default parameters for constructors above a certain size.

Workaround 1 seems kind of excessive depending on how many different parameter sizes I have.

Workaround 2 seems to disregard the entire point of a constructor, as I will be forced to assign data after calling the constructor.

Are there any other better workarounds?

like image 242
green_meep Avatar asked Oct 31 '22 23:10

green_meep


1 Answers

You can use variadic templates and perfect forwarding.

template<typename T, typename... Args>
Pet* createObject(Args&&... args) {
    return new T(std::forward<Args>(args)...);
}

However, since any pointer can be converted to its base class, it would probably be better if this function returns T*. Moreover, using naked pointers is not wise, as you'll have to delete them manually. It's better to use shared_ptr or unique_ptr. For these classes there already are similar factory methods: make_shared and make_unique (the latter only in C++14). Or, if your compiler doesn't support C++11, then you can use shared_ptr and make_shared from Boost.

Of course this solution works when you know at compile time what type you'll need to create. If you have to decide it at runtime, then the whole problem has to be considered from a different direction, as if you don't know what type you are going to create, then there is no way you can know what parameters to give them, except for parameters common for all types. In this case what you need is an abstract factory pattern. Fortunately, C++ (at least from C++11) provides a way to implement this pattern without creating a hell lot of classes. For example, let's say you have to create instances of some class derived from Pet. The actual kind of pet, its size and other attributes are decided somewhere else, while the name of the pet is decided at the time of creation. Then, you'll need a factory like this:

typedef std::function<std::shared_ptr<Pet>(const std::string& name)> PetFactory;

At some point you decide that you want to create a Dog (I leave the meaning of the actual creation parameters to your imagination).

PetFactory petFactory =
        [](const std::string& name) {
            return std::make_shared<Dog>(name, 12, "brown", 23.5);
        }

When you actually create it, all you need is to call the factory:

std::shared_ptr<Pet> pet = petFactory("Pet Name");
like image 189
petersohn Avatar answered Nov 09 '22 09:11

petersohn