C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
136: The Factory Pattern
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
Spellwith a virtual methodcast(). - Implement three derived classes:
Fireball,Heal, andTeleport. Each should print a unique message whencast()is called. - Create a
SpellFactoryclass with a static methodcreateSpell(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 returnnullptr. - 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.
There are no comments for now.