Factory Method Design Pattern: Comprehensive Explanation

The Factory Method pattern is a creational design pattern that defines an interface for creating an object but allows subclasses to alter the type of objects that will be created. It promotes loose coupling by delegating instantiation to subclasses.


Core Intent

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”

This enables parallel class hierarchies and runtime polymorphism in object creation.


When to Use Factory Method

ScenarioUse Case
A class cannot anticipate the type of objects to createFuture-proof design
Creation logic varies by subclasse.g., OS-specific dialogs
Want to localize creation knowledgeIn subclasses
Need parallel hierarchiese.g., Document → PDFDocument, WordDocument
Extensibility is keyAdd new types without changing core code

Avoid when:

  • Creation is simple → Use direct new
  • No variation in product types → Use Simple Factory (not a GoF pattern)

Where It Is Used in Real Systems

DomainExample
UI FrameworksDialog creates WindowsButton or MacButton
Document ProcessingApplication.CreateDocument() → ExcelDocument, WordDocument
Game EnginesEnemyFactory.CreateEnemy(level)
ORMsConnectionFactory.CreateConnection(dbType)
LoggingLoggerFactory.CreateLogger(format)

Key Benefits

  • Open/Closed Principle: Add new product types without modifying existing code
  • Decentralized creation: Each subclass knows its product
  • Parallel hierarchies: Product and Creator evolve together
  • Testability: Substitute mock creators

Real-World Example: E-Commerce Receipt Exporter System

An e-commerce platform needs to export order receipts in different formats depending on the context:

  • Web → HTML (for browser/email)
  • Reporting → PDF (for invoices)
  • API Integration → JSON (for third-party systems)

Each context has its own factory that decides the export format.


C# Implementation

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;

// ==================== PRODUCT INTERFACE ====================

public interface IReceiptExporter
{
    string Export(OrderReceipt receipt);
    string FileExtension { get; }
}

// ==================== CONCRETE PRODUCTS ====================

public class HtmlReceiptExporter : IReceiptExporter
{
    public string Export(OrderReceipt receipt)
    {
        var sb = new StringBuilder();
        sb.AppendLine("<!DOCTYPE html><html><body>");
        sb.AppendLine($"<h2>Receipt - {receipt.StoreName}</h2>");
        sb.AppendLine($"<p>Date: {receipt.OrderDate:yyyy-MM-dd}</p>");
        sb.AppendLine($"<p>Customer: {receipt.CustomerName}</p>");
        sb.AppendLine("<ul>");
        foreach (var item in receipt.Items)
            sb.AppendLine($"<li>{item.Name} x{item.Quantity} @ {item.UnitPrice:C} = {item.Subtotal:C}</li>");
        sb.AppendLine("</ul>");
        sb.AppendLine($"<p><strong>Total: {receipt.Total:C}</strong></p>");
        sb.AppendLine("</body></html>");
        return sb.ToString();
    }

    public string FileExtension => ".html";
}

public class PdfReceiptExporter : IReceiptExporter
{
    public string Export(OrderReceipt receipt)
    {
        // Simulate PDF generation (in real use: iText, QuestPDF, etc.)
        return $"[PDF Content for Order {receipt.OrderId} - Total: {receipt.Total:C}]";
    }

    public string FileExtension => ".pdf";
}

public class JsonReceiptExporter : IReceiptExporter
{
    public string Export(OrderReceipt receipt)
    {
        return JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
    }

    public string FileExtension => ".json";
}

// ==================== ORDER RECEIPT (SIMPLE POCO) ====================

public class OrderReceipt
{
    public string OrderId { get; set; }
    public string StoreName { get; set; } = "GlobalMart";
    public DateTime OrderDate { get; set; } = DateTime.Now;
    public string CustomerName { get; set; }
    public List<OrderItem> Items { get; set; } = new();
    public decimal Tax { get; set; }
    public decimal Shipping { get; set; }
    public decimal Total => Items.Sum(i => i.Subtotal) + Tax + Shipping;
}

public class OrderItem
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Subtotal => Quantity * UnitPrice;
}

// ==================== CREATOR (ABSTRACT) ====================

public abstract class ReceiptExporterFactory
{
    // Factory Method — subclasses override
    public abstract IReceiptExporter CreateExporter();

    // Template method: common export flow
    public string ExportReceipt(OrderReceipt receipt)
    {
        var exporter = CreateExporter();
        var content = exporter.Export(receipt);
        var filename = $"receipt_{receipt.OrderId}{exporter.FileExtension}";
        Console.WriteLine($"[Factory] Exporting using {GetType().Name} → {filename}");
        // In real app: save to file, send email, etc.
        return content;
    }
}

// ==================== CONCRETE CREATORS ====================

public class WebReceiptExporterFactory : ReceiptExporterFactory
{
    public override IReceiptExporter CreateExporter() => new HtmlReceiptExporter();
}

public class ReportReceiptExporterFactory : ReceiptExporterFactory
{
    public override IReceiptExporter CreateExporter() => new PdfReceiptExporter();
}

public class ApiReceiptExporterFactory : ReceiptExporterFactory
{
    public override IReceiptExporter CreateExporter() => new JsonReceiptExporter();
}

// ==================== CLIENT / DEMO ====================

class Program
{
    static void Main()
    {
        var receipt = new OrderReceipt
        {
            OrderId = "ORD-2025",
            CustomerName = "Bob Smith",
            Items = new List<OrderItem>
            {
                new() { Name = "Laptop", Quantity = 1, UnitPrice = 1200.00m },
                new() { Name = "Mouse", Quantity = 1, UnitPrice = 25.00m }
            },
            Tax = 98.75m,
            Shipping = 15.00m
        };

        Console.WriteLine("=== FACTORY METHOD IN E-COMMERCE ===\n");

        // Client uses different factories based on context
        ReceiptExporterFactory factory;

        // 1. Web context → HTML
        factory = new WebReceiptExporterFactory();
        Console.WriteLine(factory.ExportReceipt(receipt));
        Console.WriteLine();

        // 2. Reporting context → PDF
        factory = new ReportReceiptExporterFactory();
        Console.WriteLine(factory.ExportReceipt(receipt));
        Console.WriteLine();

        // 3. API context → JSON
        factory = new ApiReceiptExporterFactory();
        var json = factory.ExportReceipt(receipt);
        Console.WriteLine(json);
    }
}

Sample Output

=== FACTORY METHOD IN E-COMMERCE ===

[Factory] Exporting using WebReceiptExporterFactory → receipt_ORD-2025.html
<!DOCTYPE html><html><body>
<h2>Receipt - GlobalMart</h2>
<p>Date: 2025-04-05</p>
<p>Customer: Bob Smith</p>
<ul>
<li>Laptop x1 @ $1,200.00 = $1,200.00</li>
...

[Factory] Exporting using ReportReceiptExporterFactory → receipt_ORD-2025.pdf
[PDF Content for Order ORD-2025 - Total: $1,338.75]

[Factory] Exporting using ApiReceiptExporterFactory → receipt_ORD-2025.json
{
  "OrderId": "ORD-2025",
  "StoreName": "GlobalMart",
  ...
}

UML Class Diagram

Participants in the Factory Method Pattern

The classes and objects participating in this pattern include:

  • Product (IReceiptExporter)
    • Defines the interface of objects the factory method creates.
  • ConcreteProduct (HtmlReceiptExporter, PdfReceiptExporter, JsonReceiptExporter)
    • Implements the Product interface.
    • Represents the actual object being created.
  • Creator (ReceiptExporterFactory)
    • Declares the factory method, which returns a Product object.
    • May provide a default implementation.
  • ConcreteCreator (WebReceiptExporterFactory, ReportReceiptExporterFactory, ApiReceiptExporterFactory)
    • Overrides the factory method to return a specific ConcreteProduct.
  • Client
    • Uses the Creator interface.
    • Calls CreateExporter() without knowing the concrete type.

Summary Table

ParticipantRole in E-Commerce Example
ProductIReceiptExporter – export contract
ConcreteProductHtmlReceiptExporter, PdfReceiptExporter, JsonReceiptExporter
CreatorReceiptExporterFactory – declares CreateExporter()
ConcreteCreatorWebReceiptExporterFactory, etc. – override to return specific exporter
ClientProgram – selects factory based on use case

Advantages in E-Commerce

BenefitImpact
ExtensibleAdd XmlReceiptExporterFactory → no core changes
DecoupledClient doesn’t know export format
Parallel HierarchiesWeb → HTML, API → JSON
Template MethodExportReceipt() provides common flow

Factory Method vs Abstract Factory

AspectFactory MethodAbstract Factory
CreatesOne productFamily of products
ExtensibilityAdd new creator + productAdd new factory
Use CaseSingle varying objectGroup of related objects
ExampleCreateLogger()CreateUIComponents()

Conclusion

The Factory Method pattern is ideal when creation logic varies by context and you want subclasses to own instantiation. In e-commerce, it enables clean separation of export formats (HTML, PDF, JSON) while allowing new formats to be added seamlessly.

Uma Mahesh
Uma Mahesh

Author is working as an Architect in a reputed software company. He is having nearly 21+ Years of experience in web development using Microsoft Technologies.

Articles: 276