Skip to content

Factory Method Pattern

Overview

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Intent

The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Problem

Imagine that you're creating a logistics management application. The first version of your app can only handle transportation by trucks, so the bulk of your code lives inside the Truck class.

After a while, your app becomes quite popular. Each day you receive dozens of requests from sea transportation companies to incorporate sea logistics into the app.

Great news, right? But how about the code? At present, most of your code is coupled to the Truck class. Adding Ship into the app would require making changes to the entire codebase. Moreover, if later you decide to add another type of transportation to the app, you will probably need to make all of these changes again.

As a result, you will end up with pretty nasty code, riddled with conditionals that switch the app's behavior depending on the class of transportation objects.

Solution

The Factory Method pattern suggests that you replace direct object construction calls (using the new operator) with calls to a special factory method. Don't worry: the objects are still created via the new operator, but it's being called from within the factory method. Objects returned by a factory method are often referred to as "products."

At first glance, this change may look pointless: we just moved the constructor call from one part of the program to another. However, consider this: now you can override the factory method in a subclass and change the class of products being created by the method.

There's a slight limitation though: subclasses may return different types of products only if these products have a common base class or interface. Also, the factory method in the base class should have its return type declared as this interface.

Structure

UML Diagram

Factory Method Pattern Diagram 0

Participants

  1. Product (Product)

    • Defines the interface of objects the factory method creates
    • This is the abstract product type
  2. ConcreteProduct (ConcreteProductA, ConcreteProductB)

    • Implements the Product interface
    • These are the actual products created by the concrete creators
  3. Creator (Creator)

    • Declares the factory method, which returns an object of type Product
    • May also define a default implementation of the factory method that returns a default ConcreteProduct object
    • May call the factory method to create a Product object
    • Contains core business logic that relies on Product objects
  4. ConcreteCreator (ConcreteCreatorA, ConcreteCreatorB)

    • Overrides the factory method to return an instance of a ConcreteProduct
    • Each concrete creator typically corresponds to a specific product variant

Implementation

Product Interface

The Product interface declares the operations that all concrete products must implement.

// Product.h
#ifndef PRODUCT_H
#define PRODUCT_H

#include <string>

/**
 * @brief The Product interface declares the operations that all concrete products must implement.
 * 
 * This is the abstract product in the Factory Method pattern.
 */
class Product {
public:
    virtual ~Product() = default;
    virtual std::string operation() const = 0;
};

/**
 * @brief Concrete Products provide various implementations of the Product interface.
 */
class ConcreteProductA : public Product {
public:
    std::string operation() const override {
        return "Result of ConcreteProductA";
    }
};

class ConcreteProductB : public Product {
public:
    std::string operation() const override {
        return "Result of ConcreteProductB";
    }
};

#endif // PRODUCT_H

Creator Interface

The Creator class declares the factory method that returns new product objects. The key point is that the Creator's primary responsibility is not creating products—it usually contains core business logic that relies on Product objects returned by the factory method.

// Creator.h
#ifndef CREATOR_H
#define CREATOR_H

#include "Product.h"
#include <memory>
#include <string>

/**
 * @brief The Creator class declares the factory method that returns new product objects.
 * 
 * The Creator may also provide some default implementation of the factory method.
 * Despite its name, the Creator's primary responsibility is not creating products.
 * Usually, it contains some core business logic that relies on Product objects
 * returned by the factory method. Subclasses can indirectly change that business
 * logic by overriding the factory method and returning a different type of product from it.
 */
class Creator {
public:
    virtual ~Creator() = default;

    /**
     * @brief Factory Method - This is the method that subclasses will override
     * to create different types of products.
     * 
     * Note that despite its name, the Creator's primary responsibility is NOT
     * creating products. It usually contains some core business logic that relies
     * on Product objects returned by the factory method.
     */
    virtual std::unique_ptr<Product> factoryMethod() const = 0;

    /**
     * @brief This is some core business logic that relies on Product objects.
     * 
     * This method demonstrates that the Creator's code works with the abstract
     * Product interface. Subclasses can change the type of products being created
     * by overriding the factory method.
     */
    std::string someOperation() const {
        // Call the factory method to create a Product object
        std::unique_ptr<Product> product = this->factoryMethod();
        // Use the product
        std::string result = "Creator: The same creator's code has just worked with " 
                           + product->operation();
        return result;
    }
};

/**
 * @brief Concrete Creators override the factory method to change the resulting product's type.
 */
class ConcreteCreatorA : public Creator {
public:
    /**
     * @brief Override the factory method to return an instance of ConcreteProductA.
     * 
     * Note that the signature of the method still uses the abstract product type,
     * even though the concrete product is actually returned from the method.
     * This way the Creator can stay independent of concrete product classes.
     */
    std::unique_ptr<Product> factoryMethod() const override {
        return std::make_unique<ConcreteProductA>();
    }
};

class ConcreteCreatorB : public Creator {
public:
    std::unique_ptr<Product> factoryMethod() const override {
        return std::make_unique<ConcreteProductB>();
    }
};

#endif // CREATOR_H

Demo Application

The main application demonstrates how the Factory Method pattern works in practice.

// factory_method_demo.cpp
#include "Creator.h"
#include <iostream>
#include <memory>

/**
 * @brief Client code that works with an instance of a concrete creator.
 * 
 * The client code works with an instance of a concrete creator, albeit through
 * its base interface. As long as the client keeps working with the creator via
 * the base interface, you can pass it any creator's subclass.
 * 
 * @param creator A pointer to a Creator object
 */
void clientCode(const Creator& creator) {
    std::cout << "Client: I'm not aware of the creator's class, but it still works.\n"
              << creator.someOperation() << std::endl;
}

/**
 * @brief Main function demonstrating the Factory Method pattern.
 * 
 * The Factory Method pattern suggests that you replace direct object construction
 * calls (using the new operator) with calls to a special factory method.
 * Objects are still created via the new operator, but it's being called from
 * within the factory method. Objects returned by a factory method are often
 * referred to as "products."
 */
int main() {
    std::cout << "=== Factory Method Pattern Demo ===\n\n";

    std::cout << "App: Launched with ConcreteCreatorA.\n";
    std::unique_ptr<Creator> creatorA = std::make_unique<ConcreteCreatorA>();
    clientCode(*creatorA);
    std::cout << std::endl;

    std::cout << "App: Launched with ConcreteCreatorB.\n";
    std::unique_ptr<Creator> creatorB = std::make_unique<ConcreteCreatorB>();
    clientCode(*creatorB);
    std::cout << std::endl;

    return 0;
}

Output:

1
2
3
4
5
6
7
8
9
=== Factory Method Pattern Demo ===

App: Launched with ConcreteCreatorA.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with Result of ConcreteProductA

App: Launched with ConcreteCreatorB.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with Result of ConcreteProductB

This output shows how the same client code works with different creators, each producing their own product type without the client knowing the concrete classes involved.

Key Concepts

Factory Method vs Direct Instantiation

Without Factory Method:

Product* product = new ConcreteProductA();  // Direct coupling to concrete class

With Factory Method:

std::unique_ptr<Product> product = creator->factoryMethod();  // Indirect creation

Core Principles

  1. Deferred Instantiation: The Creator doesn't know which concrete product will be created until runtime
  2. Subclass Decision: Subclasses decide which class to instantiate
  3. Interface Programming: Both Creator and Client code work with abstract interfaces
  4. Parallel Hierarchies: Product hierarchy and Creator hierarchy evolve together

Applicability

Use the Factory Method pattern when:

1. Unknown Dependencies at Compile Time

When you don't know beforehand the exact types and dependencies of the objects your code should work with.

Example: A document editor that can work with different document types (Word, PDF, HTML), but you don't know which types will be added in the future.

2. Library/Framework Extension

When you want to provide users of your library or framework with a way to extend its internal components.

Example: A game engine where users can create custom enemy types by extending the base Enemy class.

3. Resource Management

When you want to save system resources by reusing existing objects instead of rebuilding them each time.

Example: Connection pools, object pools, or caching mechanisms.

4. Product Family Management

When you need to centralize product creation logic and make it easier to maintain and extend.

Example: Different UI themes or skins where each theme creates its own set of UI components.

Advantages

1. Single Responsibility Principle

✅ You can move the product creation code into one place in the program, making the code easier to support.

2. Open/Closed Principle

✅ You can introduce new types of products into the program without breaking existing client code.

3. Loose Coupling

✅ The code works with the abstract interface rather than concrete classes, reducing dependencies.

4. Flexibility

✅ Provides hooks for subclasses, allowing them to provide extended versions of objects.

5. Eliminates Class Binding

✅ The code only deals with the Product interface, making it independent of concrete product classes.

Disadvantages

1. Code Complexity

❌ The code may become more complicated since you need to introduce many new subclasses to implement the pattern. The best case scenario is when you're introducing the pattern into an existing hierarchy of creator classes.

2. Parallel Hierarchies

❌ Requires maintaining parallel class hierarchies (Product hierarchy and Creator hierarchy), which can increase maintenance burden.

3. Overhead

❌ May introduce unnecessary complexity for simple cases where direct instantiation would suffice.

Real-World Examples

Example 1: Cross-Platform UI Elements

// Abstract Product
class Button {
public:
    virtual void render() = 0;
    virtual void onClick() = 0;
};

// Concrete Products
class WindowsButton : public Button { /* ... */ };
class MacOSButton : public Button { /* ... */ };
class LinuxButton : public Button { /* ... */ };

// Abstract Creator
class Dialog {
public:
    virtual std::unique_ptr<Button> createButton() = 0;

    void render() {
        auto button = createButton();
        button->render();
    }
};

// Concrete Creators
class WindowsDialog : public Dialog {
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
};

class MacOSDialog : public Dialog {
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacOSButton>();
    }
};

Example 2: Document Processing

// Abstract Product
class Document {
public:
    virtual void open() = 0;
    virtual void save() = 0;
    virtual void close() = 0;
};

// Concrete Products
class WordDocument : public Document { /* ... */ };
class PDFDocument : public Document { /* ... */ };
class ExcelDocument : public Document { /* ... */ };

// Abstract Creator
class Application {
public:
    virtual std::unique_ptr<Document> createDocument() = 0;

    void newDocument() {
        auto doc = createDocument();
        doc->open();
        documents.push_back(std::move(doc));
    }
private:
    std::vector<std::unique_ptr<Document>> documents;
};

// Concrete Creators
class WordApplication : public Application {
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<WordDocument>();
    }
};

Example 3: Logistics System

// Abstract Product
class Transport {
public:
    virtual void deliver() = 0;
};

// Concrete Products
class Truck : public Transport {
    void deliver() override {
        std::cout << "Deliver by land in a box\n";
    }
};

class Ship : public Transport {
    void deliver() override {
        std::cout << "Deliver by sea in a container\n";
    }
};

// Abstract Creator
class Logistics {
public:
    virtual std::unique_ptr<Transport> createTransport() = 0;

    void planDelivery() {
        auto transport = createTransport();
        transport->deliver();
    }
};

// Concrete Creators
class RoadLogistics : public Logistics {
    std::unique_ptr<Transport> createTransport() override {
        return std::make_unique<Truck>();
    }
};

class SeaLogistics : public Logistics {
    std::unique_ptr<Transport> createTransport() override {
        return std::make_unique<Ship>();
    }
};

Abstract Factory

Relationship: Abstract Factory is often implemented with Factory Methods. An Abstract Factory is usually composed of a set of Factory Methods.

Difference: Abstract Factory creates families of related objects, while Factory Method creates one type of object.

Template Method

Relationship: Factory Method is a specialization of Template Method. A Factory Method may serve as a step in a large Template Method.

Difference: Template Method defines the skeleton of an algorithm, while Factory Method focuses on object creation.

Prototype

Relationship: Designs that use Factory Method can evolve toward using Prototype, which is less rigid.

Difference: Prototype creates objects by copying a prototypical instance, while Factory Method creates objects through inheritance.

Builder

Relationship: Both are creational patterns focused on object construction.

Difference: Builder focuses on constructing complex objects step by step, while Factory Method focuses on creating objects through an interface.

Common Mistakes

1. Overusing the Pattern

Wrong: Using Factory Method for every object creation, even simple ones.

Right: Use it when you need flexibility in object creation or expect to extend the product types.

2. Breaking the Abstraction

Wrong:

1
2
3
4
5
void clientCode(Creator& creator) {
    if (auto* concreteCreator = dynamic_cast<ConcreteCreatorA*>(&creator)) {
        // Type-specific logic
    }
}

Right:

1
2
3
void clientCode(const Creator& creator) {
    creator.someOperation();  // Work through the interface
}

3. Forgetting the Business Logic

Wrong: Making the Creator just a factory with no business logic.

Right: The Creator should contain business logic that uses the products created by the factory method.

Best Practices

1. Use Smart Pointers

// Modern C++ approach
virtual std::unique_ptr<Product> factoryMethod() const = 0;

2. Make Factory Methods Protected

When the factory method is only used internally by the Creator class:

protected:
    virtual std::unique_ptr<Product> factoryMethod() const = 0;

3. Provide Default Implementation

1
2
3
virtual std::unique_ptr<Product> factoryMethod() const {
    return std::make_unique<DefaultProduct>();
}

4. Use Const Correctness

virtual std::unique_ptr<Product> factoryMethod() const = 0;  // const method

5. Document the Intent

Always document what the factory method creates and why subclasses might want to override it.

Summary

The Factory Method pattern is a powerful creational design pattern that:

  • ✅ Provides an interface for creating objects in a superclass
  • ✅ Lets subclasses alter the type of objects that will be created
  • ✅ Promotes loose coupling by eliminating direct class dependencies
  • ✅ Follows the Open/Closed Principle
  • ✅ Follows the Single Responsibility Principle
  • ✅ Makes code more flexible and extensible

Use it when you need flexibility in object creation and want to provide extension points for future product types, but be mindful of the added complexity it introduces.