The Factory Design Pattern is a creational design pattern that provides an interface or method to create objects in a way that abstracts the instantiation logic from the client. It is used to encapsulate the process of object creation, making the code more maintainable and scalable.
The Factory Design Pattern is a way to create objects without specifying the exact class of the object being created.
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.
Imagine you are a restaurant. Instead of cooking your own food, you order from a menu, and the kitchen (the “factory”) prepares it. The Factory Pattern
works the same way:
- You ask a “factory” to create objects for you (like ordering food).
- You don't need to know how the objects are created (like how the kitchen cooks).
This makes your coke flexible and easy to change, because you can “order” new objects types without rewriting all your code.
Problem:
Consider the scenario, where we create objects directly in client code, without using a dedicated factory.
#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:
Ⅰ Tight Coupling with Concrete Classes
- Issue:
- In the example, the
main()
function directly instantiatesBike
orCar
using conditionals. This create a dependency on the concrete calls.
- In the example, the
- Impact:
- 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 the Open/Closed Principle (software should be open for extension but closed for modification).
- If you later add a new vehicle type or change the implementation of existing ones, you will need to modify the
Ⅱ Violation of the Single Responsibility Principle (SRP)
- Issue:
- The
main()
function is not only responsible for controlling the flow of the program but also deciding which object to create.
- The
- Impact:
- 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.
Ⅲ Scalability and Maintainability Challenges
- Issue:
- Using multiple if-else statements to determine which object to create can quickly become unmanageable as the number of object types grows.
- Impact:
- 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.
Ⅳ Code Duplication
- Issue:
- If object creation logic is spread across various parts of the code, you might end up duplicating the same instantiation logic in multiple places.
- Impact:
- 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.
Ⅴ Testing Difficulties
- Issue:
- When object creation is hard-coded into the client logic, it becomes challenging to isolate and test different parts of your code.
- Impact:
- 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.
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.
For example, encapsulating the creation logic might look like this:
Vehicle* createVehicle(const string& type) {
if (type == "bike") {
return new Bike();
} else if (type == "car") {
return new Car();
}
return nullptr;
}
int main() {
string type = "car";
Vehicle* vehicle = createVehicle(type);
if (vehicle) {
vehicle->name();
}
// Clean up memory as needed.
}
NOTE: It is the basic form of a simplest form of factory design pattern.
Complete 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;
}
};
--------------------------------------------------
Vehicle* createVehicle(const string& type) {
if (type == "bike") {
return new Bike();
} else if (type == "car") {
return new Car();
}
return nullptr;
}
--------------------------------------------------
int main() {
string type = "car";
Vehicle* vehicle = createVehicle(type);
if (vehicle) {
vehicle->name();
}
// Clean up memory as needed.
}
// Output
Car
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).
Real-Life Analogy
- Think of a pizza shop. Instead of making the pizza yourself, you place an order specifying the type of pizza you want, and the shop prepares it for you.
- The pizza shop (
factory
) hides the details of how the pizza is made and gives you the final product.
Understanding with an Example 🤷♂️
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>
class CheesePizza {
public:
void prepare() {
std::cout << "Preparing Cheese Pizza\n";
}
};
class VeggiePizza {
public:
void prepare() {
std::cout << "Preparing Veggie Pizza\n";
}
};
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.
- Centralizes creation logic.
- Improves code readability and maintainability.
- Makes the system easier to extend.
Disadvantages:
- Can introduce complexity if overused or for very simple applications.
- Requires careful management when scaling to avoid bloated factories.
Why do we need it ?
The Factory Pattern makes our lives easier by providing a way to create objects without specifying the exact class of object that will be created. It's like having a one-stop-shop for all your object creation needs.
Imagine you want to create different types of shapes like Circle
or Rectangle
. Instead of directly creating them in your main program, you ask the factory to give you the shape you need. The factory takes care of deciding which class to instantiate.
This makes your code:
- Simpler: You don't worry about the details of object creation.
- Flexible: If a new type of shape is added, you just update the factory without changing the rest of your code.
🤔 How It Works
- Define a common interface or parent class for the objects you want to create (e.g.,
Shape
). - Create different subclasses that implement or extend this interface/class (e.g.,
Circle
,Rectangle
). - Use a factory class with a method that returns objects based on input.
🤔 Components of Factory Design Pattern
The Factory Design Pattern typically consists of several key components that work together to provide a flexible and extensible way to create objects.
1️⃣ Product Interface (or Abstract Class):
- Definition: Defines a common interface or abstract class for all products that the factory creates.
- Purpose: Ensures that the client code works with objects through a common abstraction, regardless of their concrete types.
- Example:
class Pizza {
public:
virtual void prepare() const = 0; // Pure virtual function
virtual ~Pizza() = default; // Virtual destructor
};
2️⃣ Concrete Products:
- Definition: Specific implementations of the product interface. These are the objects created by the factory.
- Purpose: Represents the actual instances that are returned to the client.
- Example:
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";
}
};
3️⃣ Factory (or Creator):
- Definition: The class that contains the logic for object creation. It often provides a method to create objects of different types based on input parameters.
- Purpose: Encapsulates the object creation process and abstracts it from the client.
- Example:
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");
}
}
};
4️⃣ Client:
- Definition: The code that uses the factory to create objects. It works only with the product interface, not the concrete product classes.
- Purpose: Ensures loose coupling between the client and the product implementations.
int main() {
try {
// Client requests a cheese pizza
std::unique_ptr<Pizza> pizza = PizzaFactory::createPizza("cheese");
pizza->prepare();
// Client requests a veggie pizza
pizza = PizzaFactory::createPizza("veggie");
pizza->prepare();
} catch (const std::invalid_argument& e) {
std::cerr << e.what() << '\n';
}
return 0;
}
Relationship Between Components:
- The client interacts with the factory to request objects.
- The factory determines which concrete product to instantiate based on input parameters.
- The concrete product is returned as a pointer/reference to the product interface, ensuring the client remains decoupled from the actual implementation.
Diagram:
[Client] --> [Factory]
|
v
[Abstract Product] <-- [Concrete Products]
Example Scenario
Imagine you're implementing a tool for creating different shapes in a drawing application:
- Abstract Product:
class Shape { virtual void draw() = 0; };
- Concrete Products:
class Circle : public Shape
,class Square : public Shape
- Factory:
ShapeFactory
with a methodcreateShape(std::string type)
- Client: Code that calls
ShapeFactory::createShape("circle")
.
Example:
Let's say we need to create Circle
and Rectangle
objects.
Without Factory:
Circle* circle = new Circle();
Rectangle* rectangle = new Rectangle();
- Here we directly instantiate the objects, which ties our code to specific classes.
With Factory:
Shape* shape = ShapeFactory::createShape("circle");
- Here, the
ShapeFactory
creates the correct object for us. Our code doesn't know or care about the exact class.
Complete Code:
#include <iostream>
#include <string>
// Step 1: Common Interface
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
// Step 2: Concrete 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.
In Simple Words:
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.
More Examples
① Example 1:
Without Factory Method Design Pattern
#include <iostream>
// Library classes
class Vehicle {
public:
virtual void printVehicle() = 0;
};
class TwoWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am two wheeler\n";
}
};
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;
}
What are the problems with the above design?
- Violation of Single Responsibility Principle (SRP):
- The Client class is responsible for both creating and managing the lifetime of the Vehicle object. This violates the SRP, which states that a class should have only one reason to change. Separating concerns would improve the maintainability and readability of the code.
- Tight Coupling:
- The Client class is tightly coupled with the concrete implementations of
TwoWheeler
andFourWheeler
. This makes it difficult to extend the codebase with new types of vehicles or to switch different implementations without modifying the Client class.
- The Client class is tightly coupled with the concrete implementations of
- Lack of Flexibility:
- Adding a new type of vehicle requires modifying the Client class, violating the Open-Closed Principle (OCP), which states that a class should be open for extension but closed for modification. This limits the flexibility and scalability of the code.
How do we avoid these problem?
1 Define Factory Interface:
Create a VehicleFactory
interface with a method for creating vehicles.
// Factory Interface
class VehicleFactory {
public:
virtual std::unique_ptr<Vehicle> createVehicle() = 0;
virtual ~VehicleFactory() {}
};
Implement Concrete Factories:
Implement concrete factory classes (TwoWheelerFactory
and FourWheelerFactory
) that implement the VehicleFactory
interface and provide methods to create instances of specific types of vehicles.
// Concrete Factory for TwoWheeler
class TwoWheelerFactory : public VehicleFactory {
public:
std::unique_ptr<Vehicle> createVehicle() override {
return std::make_unique<TwoWheeler>();
}
};
// Concrete Factory for FourWheeler
class FourWheelerFactory : public VehicleFactory {
public:
std::unique_ptr<Vehicle> createVehicle() override {
return std::make_unique<FourWheeler>();
}
};
Refactor Client:
Modify the Client
class to accept a VehicleFactory
instance instead of directly instantiating vehicles. The client will request a vehicle from the factory, eliminating the need for conditional logic based on vehicle types.
// Client class
class Client {
private:
std::unique_ptr<Vehicle> pVehicle;
public:
Client(std::unique_ptr<VehicleFactory> factory) {
pVehicle = factory->createVehicle();
}
void printVehicle() {
if (pVehicle)
pVehicle->printVehicle();
}
};
Enhanced Flexibility:
With this approach, adding new types of vehicles is as simple as creating a new factory class for the new vehicle type without modifying existing client code. For example, if we want to add a ThreeWheeler
vehicle, we can create a new factory class ThreeWheelerFactory
implementing VehicleFactory
, without changing the Client class.
With Factory Method Design
#include <iostream>
// Library classes
class Vehicle {
public:
virtual void printVehicle() = 0;
virtual ~Vehicle() {}
};
-----------------------------------------------
class TwoWheeler : public Vehicle {
public:
void printVehicle() override {
std::cout << "I am two wheeler\n";
}
};
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;
}
Problems Before the Factory Design Pattern
1️⃣ Tight Coupling:
The client directly instantiates objects of concreate classes.
Example (without factory):
CheesePizza cheesePizza;
cheesePizza.prepare();
- The client must know the exact class (
cheese Pizza
) to create, which ties the client code to specific implementations. - Adding new pizza types (e.g.,
PepperoniPizza
) requires changes to the client code, violating the Open/Closed Principle (OCP).
2️⃣ Code Duplication:
Object Creation logic is repeated in multiple places.
Example:
if (type == "cheese") {
CheesePizza cheesePizza;
cheesePizza.prepare();
} else if (type == "veggie") {
VeggiePizza veggiePizza;
veggiePizza.prepare();
}
- If the creation logic for
CheesePizza
orVeggiePizza
changes (e.g., adding toppings), it must be updated everywhere the objects are instantiated. - This duplication increases maintenance overhead and risks inconsistencies.
3️⃣ Reduced Scalability:
Problem: Adding a new product (e.g., PepperoniPizza
) requires changes throughout the codebase.
- Each place where objects are created needs to be updated to include the new type.
- As the number of products grows, this becomes unmanageable.
4️⃣ Lack of abstraction:
The client directly interacts with concrete classes instead of abstractions.
Example:
CheesePizza cheesePizza;
VeggiePizza veggiePizza;
- The client doesn't rely on a common interface (
Pizza
) but directly uses the concrete implementations (CheesePizza
,VeggiePizza
). - This makes it harder to change or replace implementations without modifying the client code.
5️⃣ Lack of Centralized Control
- Problem: Object creation logic is scattered across the codebase.
- There’s no centralized place to enforce rules, constraints, or optimizations for object creation.
- For example, ensuring only one instance of a particular product type (singleton) becomes challenging.
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.