Reputation: 3750
I've been attempting to get a better understanding of using a Factory Pattern in C++ lately.
I've consulted the following resources:
How to implement the factory method pattern in C++ correctly
https://www.geeksforgeeks.org/design-patterns-set-2-factory-method/
https://sourcemaking.com/design_patterns/factory_method/cpp/1
https://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus
https://www.bogotobogo.com/DesignPatterns/factorymethod.php
https://gist.github.com/pazdera/1099562
https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns
Before somebody marks this as "Not a question", I do have some explicit questions which I'll get to in a moment. But to clarify my understanding so far first I'd like to start with an example. This is the best example I've been able to make from the information in the above links (best meaning illustrates the Factory Pattern but is as simple as possible so beginners to this pattern can plainly see the issues involved):
// FactoryPattern.cpp
#include <iostream>
#include <vector>
enum AnimalSpecies { dog, cat };
class Animal
{
public:
virtual void makeSound() = 0;
};
class Dog : public Animal
{
public:
void makeSound() { std::cout << "woof" << "\n\n"; }
};
class Cat : public Animal
{
public:
void makeSound() { std::cout << "meow" << "\n\n"; }
};
class AnimalFactory
{
public:
static Animal* makeAnimal(AnimalSpecies animalSpecies);
};
Animal* AnimalFactory::makeAnimal(AnimalSpecies animalSpecies)
{
if (animalSpecies == AnimalSpecies::dog)
{
return(new Dog());
}
else if (animalSpecies == AnimalSpecies::cat)
{
return(new Cat());
}
else
{
std::cout << "error in AnimalFactory::makeAnimal(), animalSpecies = " << animalSpecies << " does not seem to be valid" << "\n\n";
return(nullptr);
}
}
int main(void)
{
std::vector<Animal*> animals;
animals.push_back(AnimalFactory::makeAnimal(AnimalSpecies::dog));
animals.push_back(AnimalFactory::makeAnimal(AnimalSpecies::cat));
for (auto &animal : animals)
{
animal->makeSound();
}
for (auto& animal : animals)
{
delete(animal);
}
return(0);
}
The output of this program (as intended) is:
woof
meow
Here are my questions at this time:
1) Some of the reading I've done on the Factory Pattern has led me to believe that eliminating if statements is part of the benefit. The above example fails in that regard since in AnimalFactory::makeAnimal()
an if-else
has to be used to determine species. Is there a way to refactor this to remove if statements?
2) With the various changes and improvements in C++ 14/17/20 lately, many of us are moving away from pointers to avoid the possibility of memory leaks. Is there a way to refactor this so as to not use pointers?
3) What is the advantage of the Factory Pattern? i.e. in the above example why not omit class AnimalFactory
and simply make main()
as follows:
int main(void)
{
Dog dog;
dog.makeSound();
Cat cat;
cat.makeSound();
return(0);
}
Upvotes: 8
Views: 9866
Reputation: 7881
has led me to believe that eliminating if statements is part of the benefit.
IMHO the real power from the factory pattern comes from virtualizing the factory pattern:
class AnimalFactoryInterface
{
public:
virtual Animal* makeAnimal() = 0;
};
class CatFactory : public AnimalFactoryInterface
{
public:
Animal* makeAnimal() override { return new Cat(); }
};
This virtualizes how to create new animals, something impossible to do otherwise.
The switch statement constructor is useful for making animals from a config file, but that isn't really the power here. It helps when creating a generic Breeder
class which accepts how to make generic animals.
2) With the various changes and improvements in C++ 14/17/20 lately, many of us are moving away from pointers to avoid the possibility of memory leaks. Is there a way to refactor this so as to not use pointers?
Return smart pointers (std::unique_ptr<Animal>
).
3) What is the advantage of the Factory Pattern? i.e. in the above example why not omit class AnimalFactory and simply make main() as follows:
First it makes it easy to do
class Breeder {
public:
Breeder(std::unique_ptr<AnimalFactoryInterface> animal_factory);
void Breed() { animals_.push_back(animal_factory_->Create()); }
// ... more stuff
private:
std::unique_ptr<AnimalFactoryInterface> animal_factory_;
std::vector<std::unique_ptr<Animal>> animals_;
};
Second is Dependency Injection.
We should also clarify "the factory pattern".
The thing I've described in this answer is the Abstract Factory Pattern - that is, utilizing a factory interface to create objects.
Utilizing a single method with a switch statement to create objects is the Factory Method Pattern. It definitely has its utilities, but I've always found it a bit too obvious to anchor too much on as full "design pattern". But salient to this question, and to the advantages question, is that if all you're doing is simply making a series of objects, then you're right - you definitely don't need a factory class, and you don't even need a special method if all you're doing is making the classes once. A rule of thumb for any programming is do the simplest thing that works.
Upvotes: 3
Reputation: 5351
Re 1+3: You can remove the if-else by either replacing it with a switch-case statement (equivalent, but more explicit), or by creating a static map from the enum type to a pointer to the constructor. However: The Factory Pattern is not intended to eliminate all if-statements; it just encapsulates the creation logic in one place, so it won't repeat every time you need to create a new animal from the enum type.
The advantage of using a Factory is to hide away the complexity of creating an object out of a class hierarchy when the logic is not as simple as a constructor call. For example:
is_noisy
set to false.Upvotes: 2