Before diving deep into the Factory Design Pattern, let's first understand the issues that arise when object creation is tightly coupled with client code.
Example Code (Tightly Coupled Object Creation)
Suppose a scenario where we create objects in the client code.
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
public:
// Pure Virutal Function
virtual void name() = 0;
};
class Bike: public Vehicle {
public:
void name() {
cout << "Bike" << endl;
}
};
class Car: public Vehicle {
public:
void name() {
cout << "Car" << endl;
}
};
int main()
{
Vehicle* vehicle;
string type = "car"; // Change to "bike" to create a bike
// Direct instantiation based on conditional logic
if (type == "bike") {
vehicle = new Bike();
} else if ( type == "car") {
vehicle = new Car();
} else {
vehicle = nullptr;
}
if (vehicle) {
vehicle->name();
}
// free the memory here
}
// Output
Car
Although the code above looks good and it do work as expected. However, it has some issues involved with it, which are explained below:
What's Wrong With This Approach?
Tight Coupling, Hard to Extent, No Separation of Concerns, Code Duplication, Testing and Maintenance Nightmare.
1 Tight Coupling with Concrete Classes
- Problem: In the example, the
main()
function directly instantiatesBike
orCar
using conditionals. This create a dependency on the concrete calls. - Why it's bad: If you later add a new vehicle type or change the implementation of existing ones, you will need to modify the
main()
function (or wherever the object is created). This violates theOpen/Closed Principle
(software should be open for extension but closed for modification).
2 Violation of the Single Responsibility Principle (SRP)
- Problem: The
main()
function is not only responsible for controlling the flow of the program but also deciding which object to create. - Why it's bad: Mixing business logic with object creation logic makes the code harder to understand, test, and maintain. Ideally, the responsibility for creating objects should be separated from the rest of the program's logic.
3 Scalability and Maintainability Challenges
- Problem: More vehicle types = more
if-else
orswitch
cases. Using multiple if-else statements to determine which object to create can quickly become unmanageable as the number of object types grows. - Why it's bad: Every time a new vehicle type is introduced, you must modify the client code to add another condition. This not only increases the likelihood of errors but also makes the system less flexible and harder to extend.
4 Code Duplication
- Problem: If object creation logic is spread across various parts of the code, you might end up duplicating the same instantiation logic in multiple places.
- Why it's bad: Duplicated code is error-prone and increases maintenance overhead. If the instantiation process changes, you have to update it in multiple locations, increasing the risk of inconsistencies.
5 Testing Difficulties
- Problem: When object creation is hard-coded into the client logic, it becomes challenging to isolate and test different parts of your code.
- Why it's bad: It becomes harder to use mock objects or stubs in unit tests since the object creation is not abstracted. With a factory, you can easily substitute different creation strategies for testing purposes.
The Solution: Factory Pattern
The Factory Pattern solves all these problems by centralizing and abstracting object creation.
Let's first get familiar with it:
Definition:
The Factory Design Pattern is a creation design pattern that provides an interface for creating objects, but lets subclasses decide which class to instantiate.
Normally we create a object by writing new
keyword in client code, however in this case we use a factory
to handle the creation of objects.
In simple terms:
Rather than calling a constructor directly to create an object, we use a factory method to create that object based on some input or condition.
Real-World Analogy: Ordering Pizza
Imagine you walk into a pizza shop and say, “I'd like a pizza”. The shop doesn't ask you to go into the kitchen and make it yourself. Instead, it asks, “Which type? Margherita? Pepperoni? Veggie?” Based on your choice, the kitchen (factory) creates the specific pizza for you and hands it over.
You (the client), don't care how it's made or what specific class of ingredients is used. You just want your pizza. The factory (kitchen) handles the creation logic behind the scenes.
This is exactly what the Factory Pattern does in code: it creates an object based on some input without exposing the instantiation logic to the client.
Basic Structure of Factory Pattern
The Factory Pattern typically consists of the following components:
- Product Interface or Base Class: It is an interface or abstract class that defines the methods the product must implement.
- Concrete Products: The concrete classes that implement the Product interface.
- Factory: A class with a method that returns different concrete products based on input.
+-------------------------+
| <<interface>> |
| Product |
+-------------------------+
| + operation() : void |
+-------------------------+
▲
┌──────────┴──────────┐
│ │
+---------------------+ +---------------------+
| ConcreteProductA | | ConcreteProductB |
+---------------------+ +---------------------+
| + operation() : void| | + operation() : void|
+---------------------+ +---------------------+
+------------------------------+
| Factory |
+------------------------------+
| + createProduct(type):Product|
+------------------------------+
▲
│
+-----------+
| Client |
+-----------+
| - product |
+-----------+
| + main() |
+-----------+
C++ Example:
#include <iostream>
#include <memory>
// Product Interface
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
// Concrete Products
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle\n";
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing Square\n";
}
};
// Factory Class
class ShapeFactory {
public:
static std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "circle") return std::make_unique<Circle>();
else if (type == "square") return std::make_unique<Square>();
else return nullptr;
}
};
// Usage
int main() {
auto shape1 = ShapeFactory::createShape("circle");
shape1->draw();
auto shape2 = ShapeFactory::createShape("square");
shape2->draw();
return 0;
}
Now let's solve our first example.
#include <iostream>
#include <string>
using namespace std;
-------------------------------------------------
// Product Interface
-------------------------------------------------
class Vehicle {
public:
// Pure Virutal Function
virtual void name() = 0;
};
--------------------------------------------------
// Concrete Product 1
--------------------------------------------------
class Bike: public Vehicle {
public:
void name() {
cout << "Bike" << endl;
}
};
--------------------------------------------------
// Concrete Product 2
--------------------------------------------------
class Car: public Vehicle {
public:
void name() {
cout << "Car" << endl;
}
};
--------------------------------------------------
// Factory Method
--------------------------------------------------
Vehicle* createVehicle(const string& type) {
if (type == "bike") {
return new Bike();
} else if (type == "car") {
return new Car();
}
return nullptr;
}
--------------------------------------------------
// Client
--------------------------------------------------
int main() {
string type = "car";
Vehicle* vehicle = createVehicle(type);
if (vehicle) {
vehicle->name();
}
// Clean up memory as needed.
}
// Output
Car
NOTE: It is the basic form of a simplest form of factory design pattern.
How a Factory Pattern Can Help
By encapsulating the object creation logic into a separate factory function or class, we can address these problems:
- Decoupling: The client code only needs to know about an abstract interface, not the concrete classes.
- Single Responsibility: The creation logic is centralized, keeping the client code focused on business logic.
- Extensibility: New types can be added with minimal changes, often only in the factory itself.
- Maintainability: Changes in object creation are isolated, reducing the risk of bugs and making the code easier to manage.
Key Concepts
What Is It?
- A factory is a class or method that encapsulates the object creation process.
- The client doesn't need to know the specific class to instantiate; it simply requests an object from the factory and works with it through a common interface.
Why Use It?
- To reduce tight coupling between the client and concrete classes.
- To centralize and control object creation logic.
- To follow SOLID principles, especially the Open/Closed Principle (open for extension, closed for modification).
Examples🤷♂️
Pizza Order
Imagine you’re building a program for a pizza shop. There are different types of pizzas (CheesePizza
, VeggiePizza
, etc.), and the shop’s customer interface (client) should not worry about how these pizzas are created.
Without the Factory Pattern:
#include <iostream>
#include <string>
--------------------------------------------------
// Concrete Product 1
--------------------------------------------------
class CheesePizza {
public:
void prepare() {
std::cout << "Preparing Cheese Pizza\n";
}
};
--------------------------------------------------
// Concrete Product 2
--------------------------------------------------
class VeggiePizza {
public:
void prepare() {
std::cout << "Preparing Veggie Pizza\n";
}
};
--------------------------------------------------
// Client
--------------------------------------------------
int main() {
std::string pizzaType = "cheese";
if (pizzaType == "cheese") {
CheesePizza pizza;
pizza.prepare();
} else if (pizzaType == "veggie") {
VeggiePizza pizza;
pizza.prepare();
}
return 0;
}
Problems:
- Tight Coupling: The client knows the exact pizza classes (
CheesePizza
,VeggiePizza
). - Code Duplication: If the pizza creation logic changes, it must be updated in multiple places.
- Scalability Issues: Adding a new pizza type requires changes in the
if-else
structure, violating the Open/Closed Principle.
With the Factory Pattern:
#include <iostream>
#include <memory>
#include <string>
--------------------------------------------------
// Step 1: Abstract Product Interface
--------------------------------------------------
class Pizza {
public:
virtual void prepare() const = 0; // Pure virtual function
virtual ~Pizza() = default; // Virtual destructor
};
--------------------------------------------------
// Step 2: Concrete Products
--------------------------------------------------
class CheesePizza : public Pizza {
public:
void prepare() const override {
std::cout << "Preparing Cheese Pizza\n";
}
};
class VeggiePizza : public Pizza {
public:
void prepare() const override {
std::cout << "Preparing Veggie Pizza\n";
}
};
--------------------------------------------------
// Step 3: Factory Class
--------------------------------------------------
class PizzaFactory {
public:
static std::unique_ptr<Pizza> createPizza(const std::string& type) {
if (type == "cheese") {
return std::make_unique<CheesePizza>();
} else if (type == "veggie") {
return std::make_unique<VeggiePizza>();
} else {
throw std::invalid_argument("Invalid pizza type");
}
}
};
--------------------------------------------------
// Step 4: Client Code
--------------------------------------------------
int main() {
try {
// Request a Cheese Pizza
std::unique_ptr<Pizza> pizza = PizzaFactory::createPizza("cheese");
pizza->prepare();
// Request a Veggie Pizza
pizza = PizzaFactory::createPizza("veggie");
pizza->prepare();
} catch (const std::invalid_argument& e) {
std::cerr << e.what() << '\n';
}
return 0;
}
How the Factory Pattern Improves Design:
- Encapsulation of Object Creation
- The
PizzaFactory
handles the creation ofCheesePizza
andVeggiePizza
, so the client doesn't need to know the specifics of object instantiation.
- The
- Abstraction
- The client only works with the abstract
Pizza
interface. It doesn’t interact directly with concrete implementations (CheesePizza
,VeggiePizza
).
- The client only works with the abstract
- Open/Closed Principle
- Adding a new pizza type (e.g.,
PepperoniPizza
) only requires changes in the factory, not in the client code.
- Adding a new pizza type (e.g.,
- Centralized Object Creation
- All creation logic is in one place (
PizzaFactory
), making it easier to modify and maintain.
- All creation logic is in one place (
Advantages of the Factory Pattern:
- Promotes loose coupling:
- The client code is decoupled from the actual instantiation of classes.
- You work with interfaces rather than the concrete classes.
- Enhances Extensibility (OCP - Open/Closed Principle):
- You can introduce new classes (e.g., new types of pizzas) without modifying existing client code.
- The system becomes easier to scale and extend.
- Centralizes object creation (SRP - Single Responsibility Principle):
- The responsibility of object creation is moved to a dedicated factory class.
- Business logic stays clean and focused only on “What to do” with the object not on “How to do”.
- Increases Flexibility:
- The decision of “which object to create” can be deferred to runtime based on input, config, or logic.
- Make your system adaptable to dynamic requirements.
- Improves Code Reusability:
- Common instantiation logic can be reused from a single factory.
- Avoids code duplication when creating similar objects in different parts of the system.
Disadvantages:
- Increased Complexity:
- Introduces additional layers (factory classes/interfaces) which might be overkill for very small programs.
- More Code Overhead:
- Requires writing extra code like factory classes and interfaces, which might look unnecessary in simpler use-cases.
Example 2 (Shape)
#include <iostream>
#include <string>
--------------------------------------------------
// Step 1: Product Interface
--------------------------------------------------
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
--------------------------------------------------
// Step 2: Concrete Product Implementations
--------------------------------------------------
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a Circle.\n";
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing a Rectangle.\n";
}
};
--------------------------------------------------
// Step 3: Factory Class
--------------------------------------------------
class ShapeFactory {
public:
static Shape* createShape(const std::string& type) {
if (type == "circle") {
return new Circle();
} else if (type == "rectangle") {
return new Rectangle();
}
return nullptr;
}
};
--------------------------------------------------
// Step 4: Client Code
--------------------------------------------------
int main() {
Shape* shape1 = ShapeFactory::createShape("circle");
if (shape1) shape1->draw(); // Output: Drawing a Circle.
Shape* shape2 = ShapeFactory::createShape("rectangle");
if (shape2) shape2->draw(); // Output: Drawing a Rectangle.
delete shape1;
delete shape2;
return 0;
}
What's Happening?
- Client asks the
ShapeFactory
for acircle
orrectangle
. - The factory decides which class to instantiate and gives the correct object.
- The client just uses the object without worrying about its exact class.
Example 3:
The Factory Design Pattern is like a "vending machine."
You press a button (give input), and the vending machine (factory) gives you the item (object) you want, without you needing to know how it was made or stored.
Without Factory Method Design Pattern
#include <iostream>
-----------------------------------------------
// Product Interface
-----------------------------------------------
class Vehicle {
public:
virtual void printVehicle() = 0;
};
-----------------------------------------------
// Concrete Product 1
-----------------------------------------------
class TwoWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am two wheeler\n";
}
};
-----------------------------------------------
// Concrete Product 2
-----------------------------------------------
class FourWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am four wheeler\n";
}
};
-----------------------------------------------
// Client (or user) class
-----------------------------------------------
class Client {
private:
Vehicle* pVehicle;
public:
Client(int type) {
if (type == 1) {
pVehicle = new TwoWheeler();
} else if (type == 2) {
pVehicle = new FourWheeler();
} else {
pVehicle = nullptr;
}
}
~Client() {
delete pVehicle;
}
Vehicle* getVehicle() {
return pVehicle;
}
};
// Driver program
int main() {
Client* pClient = new Client(1);
Vehicle* pVehicle = pClient->getVehicle();
if (pVehicle != nullptr) {
pVehicle->printVehicle();
}
delete pClient;
return 0;
}
No factory used, the object creation logic in the main function.
With Factory Method Design
#include <iostream>
-----------------------------------------------
// Product Interface
-----------------------------------------------
class Vehicle {
public:
virtual void printVehicle() = 0;
virtual ~Vehicle() {}
};
-----------------------------------------------
// Conrete Product 1
-----------------------------------------------
class TwoWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am two wheeler\n";
}
};
-----------------------------------------------
// Concrete Product 2
-----------------------------------------------
class FourWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am four wheeler\n";
}
};
-----------------------------------------------
// Factory Interface
-----------------------------------------------
class VehicleFactory {
public:
virtual Vehicle* createVehicle() = 0;
virtual ~VehicleFactory() {}
};
-----------------------------------------------
// Concrete Factory for TwoWheeler
-----------------------------------------------
class TwoWheelerFactory : public VehicleFactory {
public:
Vehicle* createVehicle() override {
return new TwoWheeler();
}
};
-----------------------------------------------
// Concrete Factory for FourWheeler
-----------------------------------------------
class FourWheelerFactory : public VehicleFactory {
public:
Vehicle* createVehicle() override {
return new FourWheeler();
}
};
-----------------------------------------------
// Client class
-----------------------------------------------
class Client {
private:
Vehicle* pVehicle;
public:
Client(VehicleFactory* factory) {
pVehicle = factory->createVehicle();
}
~Client() {
delete pVehicle;
}
Vehicle* getVehicle() {
return pVehicle;
}
};
-----------------------------------------------
// Driver program
-----------------------------------------------
int main() {
VehicleFactory* twoWheelerFactory = new TwoWheelerFactory();
Client* twoWheelerClient = new Client(twoWheelerFactory);
Vehicle* twoWheeler = twoWheelerClient->getVehicle();
twoWheeler->printVehicle();
delete twoWheelerClient;
VehicleFactory* fourWheelerFactory = new FourWheelerFactory();
Client* fourWheelerClient = new Client(fourWheelerFactory);
Vehicle* fourWheeler = fourWheelerClient->getVehicle();
fourWheeler->printVehicle();
delete fourWheelerClient;
return 0;
}
In the above code:
- Vehicle serves as the Product interface, defining the common method printVehicle() that all concrete products must implement.
- TwoWheeler and FourWheeler are concrete product classes representing different types of vehicles, implementing the printVehicle() method.
- VehicleFactory acts as the Creator interface (Factory Interface) with a method createVehicle() representing the factory method.
- TwoWheelerFactory and FourWheelerFactory are concrete creator classes (Concrete Factories) implementing the VehicleFactory interface to create instances of specific types of vehicles.
#include <iostream>
#include <string>
// Base class representing a generic product
class Product {
public:
// Constructor for Product
Product() { std::cout << "Product" << std::endl; }
// Virtual destructor for proper cleanup in derived classes
virtual ~Product() {}
// Pure virtual function that must be implemented by derived classes
virtual void checkProduct() = 0;
};
// Concrete implementation of Product: ConcreateProductA
class ConcreateProductA : public Product {
public:
// Constructor for ConcreateProductA
ConcreateProductA() { std::cout << "ConcreateProductA" << std::endl; }
// Destructor for ConcreateProductA
~ConcreateProductA() {}
// Implementation of the checkProduct method
void checkProduct() { std::cout << "ProductA has been created" << std::endl; }
};
// Concrete implementation of Product: ConcreateProductB
class ConcreateProductB : public Product {
public:
// Constructor for ConcreateProductB
ConcreateProductB() { std::cout << "ConcreateProductB" << std::endl; }
// Destructor for ConcreateProductB
~ConcreateProductB() {}
// Implementation of the checkProduct method
void checkProduct() { std::cout << "ProductB has been created" << std::endl; }
};
// Abstract Creator class that declares factory methods
class Creater {
public:
// Constructor for Creater
Creater() { std::cout << "Creater" << std::endl; }
// Virtual destructor for proper cleanup in derived classes
virtual ~Creater() {}
// Factory method for creating ProductA (pure virtual, must be implemented by derived classes)
virtual Product* createProductA() = 0;
// Factory method for creating ProductB (pure virtual, must be implemented by derived classes)
virtual Product* createProductB() = 0;
};
// Concrete Creator that implements the factory methods
class ConcreateCreater : public Creater {
public:
// Constructor for ConcreateCreater
ConcreateCreater() { std::cout << "ConcreateCreater" << std::endl; }
// Destructor for ConcreateCreater
~ConcreateCreater() {}
// Factory method implementation for creating ProductA
Product* createProductA() { return new ConcreateProductA; }
// Factory method implementation for creating ProductB
Product* createProductB() { return new ConcreateProductB; }
};
int main(int argc, char* argv[]) {
// Create an instance of ConcreateCreater
Creater *creater = new ConcreateCreater;
// Use the factory method to create a ProductA instance
Product *productA = creater->createProductA();
productA->checkProduct(); // Call checkProduct on ProductA
// Use the factory method to create a ProductB instance
Product *productB = creater->createProductB();
productB->checkProduct(); // Call checkProduct on ProductB
// Clean up dynamically allocated memory
delete productA;
delete productB;
delete creater;
return 0;
}
Different Types of Factory Patterns
Factory patterns are a group of creational design patterns that provide ways to encapsulate object creation, promoting loose coupling, and flexibility. The three most common factory
approaches are:
1️⃣ Simple Factory
Although not officially one of the "Gang of Four" patterns, the Simple Factory is a common technique where a single function (or method) creates objects based on input parameters.
How It Works:
A function takes some form of input (often a string or an enum) and returns an instance of one of several concrete classes that share a common interface.
Example:
Step 1: Define a Base Class (Interface)
// All Vehicle will inherit from this.
class Vehicle {
public:
virtual void name() = 0; // Pure virtual function (must be overridden)
virtual ~Vehicle() = default; // Good practice for polymorphism.
};
Step 2: Create Concrete Classes
class Bike: public Vehicle {
public:
void name() override {
cout << "Bike" << endl;
}
};
class Car: public Vehicle {
public:
void name() override {
cout << "Car" << endl;
}
};
Step 3: Build the Factory
class VehicleFactory {
public:
// Creates a vehicle based on the type passed
// (e.g., "car" or "bike"
static Vehicle* createVehicle(const string& type) {
if (type == "car") {
return new Car();
} else if (type == "bike") {
return new Bike();
}
// some other type
return nullptr;
}
};
Step 4: Use the Factory
int main() {
Vehicle* myVehicle = VehicleFactory::createVehicle("bike");
if (myVehicle) {
myVehicle->name();
}
}
Complete Code:
#include <iostream>
#include <string>
using namespace std;
// All Vehicle will inherit from this.
class Vehicle {
public:
virtual void name() = 0; // Pure virtual function (must be overridden)
virtual ~Vehicle() = default; // Good practice for polymorphism.
};
class Bike: public Vehicle {
public:
void name() override {
cout << "Bike" << endl;
}
};
class Car: public Vehicle {
public:
void name() override {
cout << "Car" << endl;
}
};
class VehicleFactory {
public:
// Creates a vehicle based on the type passed
// (e.g., "car" or "bike"
// made static so that we don't need to create
// object of it, can be called directly.
static Vehicle* createVehicle(const string& type) {
if (type == "car") {
return new Car();
} else if (type == "bike") {
return new Bike();
}
// some other type
return nullptr;
}
};
int main() {
Vehicle* myVehicle = VehicleFactory::createVehicle("bike");
if (myVehicle) {
myVehicle->name();
}
}
- Use Case:
- Simple and effective for situations where object creation logic is relatively straightforward.
2️⃣ Factory Method Pattern
The Factory Method pattern defines an interface for creating an object but lets subclasses decide which class to instantiate. This pattern defers instantiation to subclasses.
Each branch of your code has its own factory.
How It Works:
- Creator (Abstract Class): Declares the factory method which returns an object of a product interface.
- Concrete Creators: Override the factory method to create and return a specific product.
Example:
// Product Interface
class Vehicle {
public:
virtual void name() = 0;
virtual ~Vehicle() = default;
};
// Concrete Products
class Bike : public Vehicle {
public:
void name() override { std::cout << "Bike" << std::endl; }
};
class Car : public Vehicle {
public:
void name() override { std::cout << "Car" << std::endl; }
};
// Creator Abstract Class
class VehicleFactory {
public:
virtual Vehicle* createVehicle() const = 0;
virtual ~VehicleFactory() = default;
};
// Concrete Creators
class BikeFactory : public VehicleFactory {
public:
Vehicle* createVehicle() const override { return new Bike(); }
};
class CarFactory : public VehicleFactory {
public:
Vehicle* createVehicle() const override { return new Car(); }
};
// Usage
int main() {
std::unique_ptr<VehicleFactory> factory = std::make_unique<CarFactory>();
std::unique_ptr<Vehicle> vehicle(factory->createVehicle());
vehicle->name(); // Output: Car
return 0;
}
- Use Case:
- Useful when a class can’t anticipate the type of objects it needs to create, or when you want to delegate the responsibility of instantiation to subclasses.
3️⃣ Abstract Factory Design Pattern
We will discuss it in the next chapter.