cdahms
cdahms

Reputation: 3750

C++ Factory Pattern implementation - example, questions, and concerns

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

Answers (2)

IdeaHat
IdeaHat

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

Little Bobby Tables
Little Bobby Tables

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:

  1. There are many settings involved, so you want to create an object that creates all objects with the same settings; e.g. an Animals factory that creates all animals with a flag is_noisy set to false.
  2. The object creation logic is not trivial, e.g. creating an animal from a configuration string in JSON format.
  3. There are additional global aspects, like counting all allocated animals.

Upvotes: 2

Related Questions