The Factory Method Pattern and Letting a Factory Decide What Gets Built
When you need an object but cannot know which exact type to build until the moment you need it, handing that decision to a factory beats deciding it yourself.
Here's the idea in one sentence: when you need an object but don't know which exact type to build until the moment you need it, hand that decision to a factory instead of deciding it yourself.
When You Don't Know Which Object You Need Yet
Constructor injection works great when you already know which object you want, you just don't want to build it right there. You hand the finished object in, and the class using it never has to know how it was built.
But sometimes you genuinely don't know which object you need until the exact moment you need it. Picture a zoo simulator that spawns a new animal every so often, sometimes a dog, sometimes a cat, sometimes a duck. Nobody can hand you "the animal" ahead of time, because which animal it should be depends on logic that only runs the moment you ask for one.
That's the whole problem this pattern solves: something has to decide which concrete type to build, and that decision itself deserves its own place to live.
Two Factories, Same Animal, Different Logic
Picture two different ways of deciding which animal to spawn next. One picks completely at random. The other keeps count, and always builds whichever animal has been built the least so far, so the zoo stays roughly balanced.
Both are called the exact same way. What differs is only what happens inside.
class Animal {
public:
virtual ~Animal() = default;
virtual std::string name() const = 0;
};
class Dog : public Animal {
public:
std::string name() const override { return "Dog"; }
};
class Cat : public Animal {
public:
std::string name() const override { return "Cat"; }
};
class Duck : public Animal {
public:
std::string name() const override { return "Duck"; }
};
class AnimalFactory {
public:
virtual ~AnimalFactory() = default;
virtual std::unique_ptr<Animal> createAnimal() = 0;
};Now the two factories, each implementing createAnimal() a completely different way.
class RandomAnimalFactory : public AnimalFactory {
public:
std::unique_ptr<Animal> createAnimal() override {
int choice = std::rand() % 3;
if (choice == 0) return std::make_unique<Dog>();
if (choice == 1) return std::make_unique<Cat>();
return std::make_unique<Duck>();
}
};
class BalancedAnimalFactory : public AnimalFactory {
public:
std::unique_ptr<Animal> createAnimal() override {
if (dogCount_ <= catCount_ && dogCount_ <= duckCount_) {
dogCount_++;
return std::make_unique<Dog>();
}
if (catCount_ <= duckCount_) {
catCount_++;
return std::make_unique<Cat>();
}
duckCount_++;
return std::make_unique<Duck>();
}
private:
int dogCount_ = 0;
int catCount_ = 0;
int duckCount_ = 0;
};Notice RandomAnimalFactory has no fields; it doesn't need to remember anything between calls. BalancedAnimalFactory does, it has to remember the count of each animal to stay balanced. Same job, same method name, genuinely different behavior. That's exactly the kind of variation worth a pattern for.
Swapping the Factory Itself
Because both factories share the same shape, any code that uses one doesn't need to know or care which one it got.
void populateZoo(AnimalFactory& factory, int count) {
for (int i = 0; i < count; ++i) {
auto animal = factory.createAnimal();
std::cout << animal->name() << "\n";
}
}
RandomAnimalFactory randomFactory;
populateZoo(randomFactory, 6);
BalancedAnimalFactory balancedFactory;
populateZoo(balancedFactory, 6);populateZoo never mentions Dog, Cat, Duck, RandomAnimalFactory, or BalancedAnimalFactory by name. Swap which factory gets passed in, and the zoo's whole spawning behavior changes without touching populateZoo at all.
ExpandRandomAnimalFactory and BalancedAnimalFactory both implement AnimalFactory, each deciding differently which of Dog, Cat, or Duck to build
The next post looks at the general shape behind this, a common mistake that looks like this pattern but isn't, and where it actually pays off at scale.
The Essentials
- Dependency injection needs the caller to already know which object to hand over. A factory is for when nobody knows which concrete type is needed until the exact moment it's requested.
- Two factories can build the exact same product type through completely different logic. Because they share one shape, either one can be swapped in without changing the code that uses it.
Keep reading