Skip to Content
Course content

136: The Factory Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running a high-end pizza shop. When a customer walks in, they don't go into the kitchen, grab a handful of flour, find the pepperoni, and start assembling the pizza themselves. That would be chaos. Instead, they talk to a cashier. They say, "I want a Pepperoni Pizza," and the cashier handles the communication with the kitchen. The customer doesn't need to know which oven is being used or who the chef is; they just know that if they ask for a "Pepperoni," they get a Pizza object back.

In C++, we often fall into the trap of using new (or std::make_unique) all over our business logic. If your GameManager class is littered with if (type == "Orc") { enemy = std::make_unique(); }, you've coupled your high-level logic to every single concrete enemy class in your game. Every time you add a new enemy type, you have to hunt down every piece of code that instantiates enemies and add another else if. It's a maintenance nightmare.

Decoupling the 'What' from the 'How'

The Factory Pattern fixes this by moving the instantiation logic into its own dedicated class. We map the pizza shop analogy directly to the code like this:

  • The Customer: Your high-level game logic that needs an object.
  • The Cashier: The Factory class.
  • The Pizza: The base class (or interface) that defines what the object can do.
  • Pepperoni/Veggie: The concrete derived classes.

Here is how I usually structure a basic factory in a real project. I'll use a game enemy example because it's where this pattern really shines.

#include <iostream>
#include <memory>
#include <string>

// The "Pizza" base class
class Enemy {
public:
    virtual ~Enemy() = default;
    virtual void attack() = 0;
};

// Concrete "Pepperoni" and "Veggie" versions
class Orc : public Enemy {
public:
    void attack() override { std::cout << "Orc swings a heavy axe!\n"; }
};

class Goblin : public Enemy {
public:
    void attack() override { std::cout << "Goblin stabs with a rusty dagger!\n"; }
};

// The "Cashier" (The Factory)
class EnemyFactory {
public:
    enum class EnemyType { Orc, Goblin };

    static std::unique_ptr createEnemy(EnemyType type) {
        switch (type) {
            case EnemyType::Orc:    return std::make_unique();
            case EnemyType::Goblin: return std::make_unique();
            default:                return nullptr;
        }
    }
};

int main() {
    // The client doesn't know Orc or Goblin exists. 
    // It only knows about the Enemy interface and the Factory.
    auto myEnemy = EnemyFactory::createEnemy(EnemyFactory::EnemyType::Orc);
    myEnemy->attack();
    
    return 0;
}

Why this is actually useful

You might be thinking, "I just moved the if/else block to a different class. What's the win?"

The win is isolation. Your main() function or your LevelLoader class no longer needs to #include "Orc.h" or #include "Goblin.h". It only needs the Factory and the Base class. This drastically reduces compile times in large projects because changing the internal implementation of an Orc doesn't force the entire game engine to recompile—only the Factory does.

I've also seen people take this a step further by using a std::map of function pointers or "creator" objects inside the factory. This allows you to register new types at runtime without even changing the factory's switch statement. But for most of your needs, the simple static method approach I showed above is the sweet spot between over-engineering and clean architecture.




📋 Practical Task

Building a Dynamic Spell-Casting System

You are building a magic system for an RPG. Instead of manually instantiating different spell types in your combat loop, you need to implement a Factory.

Your requirements:

  • Create a base class Spell with a virtual method cast().
  • Implement three derived classes: Fireball, Heal, and Teleport. Each should print a unique message when cast() is called.
  • Create a SpellFactory class with a static method createSpell(std::string spellName).
  • The factory should return a std::unique_ptr<Spell>. If a string is passed that doesn't match a known spell, it should return nullptr.
  • In your main() function, simulate a player typing spell names into a console. Loop through a list of strings (e.g., {"Fireball", "Heal", "InvalidSpell", "Teleport"}) and use the factory to cast them if they are valid.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.