The Factory Pattern is a creational design pattern that provides a way to create objects without specifying their exact concrete classes. Instead of calling a constructor directly, you ask a factory to create an object for you based on a parameter or configuration logic.

The core benefit: your client code doesn’t need to know or care which concrete class it’s working with. If you add a new type later, you only modify the factory — not every place in the application that creates objects.


Why Use It?

Imagine you’re building a database connection library. Your users want to connect to different databases: PostgreSQL, MySQL, SQLite. Without a factory, your code is tightly coupled:

// ❌ Tightly coupled — client knows all concrete types
if (dbType == "postgres") {
  connection = new PostgresConnection();
} else if (dbType == "mysql") {
  connection = new MySqlConnection();
} else if (dbType == "sqlite") {
  connection = new SqliteConnection();
}

Every time you add a new database type, you have to update this if-else chain everywhere. That’s maintenance hell.

With a factory, the client delegates creation to a single place:

// ✓ Decoupled — client just asks the factory
connection = DatabaseFactory::create("postgres");

Clean. Scalable. Testable. The logic for “how do I create a PostgreSQL connection?” is encapsulated in one place.


Interactive Implementation Across Languages

Compare how to implement the Factory Pattern across C++, Python, JavaScript, and Java:

DatabaseFactory.cpp
// Base class (interface)
class DatabaseConnection {
public:
  virtual ~DatabaseConnection() = default;
  virtual void connect() = 0;
  virtual void query(const std::string& sql) = 0;
};

// Concrete implementations
class PostgresConnection : public DatabaseConnection {
public:
  void connect() override { std::cout << "Connecting to PostgreSQL\n"; }
  void query(const std::string& sql) override {
      std::cout << "Executing on Postgres: " << sql << "\n";
  }
};

class MySqlConnection : public DatabaseConnection {
public:
  void connect() override { std::cout << "Connecting to MySQL\n"; }
  void query(const std::string& sql) override {
      std::cout << "Executing on MySQL: " << sql << "\n";
  }
};

class SqliteConnection : public DatabaseConnection {
public:
  void connect() override { std::cout << "Connecting to SQLite\n"; }
  void query(const std::string& sql) override {
      std::cout << "Executing on SQLite: " << sql << "\n";
  }
};

// THE FACTORY
class DatabaseFactory {
public:
  static std::unique_ptr<DatabaseConnection> create(
      const std::string& type
  ) {
      if (type == "postgres") {
          return std::make_unique<PostgresConnection>();
      } else if (type == "mysql") {
          return std::make_unique<MySqlConnection>();
      } else if (type == "sqlite") {
          return std::make_unique<SqliteConnection>();
      }
      throw std::invalid_argument("Unknown database type: " + type);
  }
};

// CLIENT CODE
int main() {
  auto db = DatabaseFactory::create("postgres");
  db->connect();
  db->query("SELECT * FROM users");

  auto db2 = DatabaseFactory::create("mysql");
  db2->connect();
  db2->query("INSERT INTO logs VALUES (...)");
}

Key Takeaways

  • Decoupling: Client code doesn’t depend on concrete classes, only the factory interface.
  • Extensibility: Want to add a new database type? Just create a new class and register it in the factory. Zero client code changes.
  • Single Responsibility: All object creation logic lives in one place — the factory.
  • Testability: Mock the factory or swap implementations for testing without touching client code.

When NOT to Use the Factory Pattern

The factory pattern isn’t a silver bullet. Avoid it when:

  1. You only have one or two concrete types that never change. A simple if-else or direct constructor is clearer.
  2. Object creation is so complex it needs a builder pattern.
  3. You’re dealing with simple value objects without polymorphism.