๐Ÿงฉ

Factory Patterns

Design Patterns Intermediate 2 min read 200 words

Create objects without specifying their exact classes

Factory Patterns

Factory patterns encapsulate object creation, hiding the instantiation logic from client code. There are several variations:

  1. Simple Factory - A method that creates objects
  2. Factory Method - Subclasses decide which class to instantiate
  3. Abstract Factory - Create families of related objects

Simple Factory

The simplest form - a static method or class that creates objects.

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine($"[Console] {message}");
}

public class FileLogger : ILogger
{
    private readonly string _path;
    public FileLogger(string path) => _path = path;
    public void Log(string message) => File.AppendAllText(_path, $"{message}\n");
}

public class CloudLogger : ILogger
{
    public void Log(string message) => Console.WriteLine($"[Cloud] {message}");
}

// Simple Factory
public static class LoggerFactory
{
    public static ILogger Create(string type, string config = null)
    {
        return type.ToLower() switch
        {
            "console" => new ConsoleLogger(),
            "file" => new FileLogger(config ?? "app.log"),
            "cloud" => new CloudLogger(),
            _ => throw new ArgumentException($"Unknown logger type: {type}")
        };
    }
}

// Usage
ILogger logger = LoggerFactory.Create("console");
logger.Log("Application started");

ILogger fileLogger = LoggerFactory.Create("file", "errors.log");
fileLogger.Log("An error occurred");

Factory Method Pattern

Defines an interface for creating objects, but lets subclasses decide which class to instantiate.

// Product interface
public interface IDocument
{
    void Open();
    void Save();
    void Close();
}

// Concrete products
public class WordDocument : IDocument
{
    public void Open() => Console.WriteLine("Opening Word document");
    public void Save() => Console.WriteLine("Saving Word document");
    public void Close() => Console.WriteLine("Closing Word document");
}

public class PdfDocument : IDocument
{
    public void Open() => Console.WriteLine("Opening PDF document");
    public void Save() => Console.WriteLine("Saving PDF document");
    public void Close() => Console.WriteLine("Closing PDF document");
}

public class ExcelDocument : IDocument
{
    public void Open() => Console.WriteLine("Opening Excel spreadsheet");
    public void Save() => Console.WriteLine("Saving Excel spreadsheet");
    public void Close() => Console.WriteLine("Closing Excel spreadsheet");
}

// Creator with factory method
public abstract class Application
{
    // Factory method - subclasses override to create specific document
    protected abstract IDocument CreateDocument();

    public void NewDocument()
    {
        IDocument doc = CreateDocument();
        doc.Open();
    }

    public void SaveDocument()
    {
        IDocument doc = CreateDocument();
        doc.Save();
    }
}

// Concrete creators
public class WordApplication : Application
{
    protected override IDocument CreateDocument() => new WordDocument();
}

public class PdfApplication : Application
{
    protected override IDocument CreateDocument() => new PdfDocument();
}

public class ExcelApplication : Application
{
    protected override IDocument CreateDocument() => new ExcelDocument();
}

// Usage
Application app = new WordApplication();
app.NewDocument(); // "Opening Word document"

app = new PdfApplication();
app.NewDocument(); // "Opening PDF document"

Abstract Factory Pattern

Creates families of related objects without specifying their concrete classes.

// Abstract products
public interface IButton
{
    void Render();
    void OnClick(Action action);
}

public interface ITextBox
{
    void Render();
    string GetText();
    void SetText(string text);
}

public interface ICheckBox
{
    void Render();
    bool IsChecked { get; set; }
}

// Abstract factory
public interface IUIFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
    ICheckBox CreateCheckBox();
}

// Windows family
public class WindowsButton : IButton
{
    public void Render() => Console.WriteLine("[Windows Button]");
    public void OnClick(Action action) => action();
}

public class WindowsTextBox : ITextBox
{
    private string _text = "";
    public void Render() => Console.WriteLine($"[Windows TextBox: {_text}]");
    public string GetText() => _text;
    public void SetText(string text) => _text = text;
}

public class WindowsCheckBox : ICheckBox
{
    public void Render() => Console.WriteLine($"[Windows CheckBox: {IsChecked}]");
    public bool IsChecked { get; set; }
}

public class WindowsUIFactory : IUIFactory
{
    public IButton CreateButton() => new WindowsButton();
    public ITextBox CreateTextBox() => new WindowsTextBox();
    public ICheckBox CreateCheckBox() => new WindowsCheckBox();
}

// macOS family
public class MacButton : IButton
{
    public void Render() => Console.WriteLine("(Mac Button)");
    public void OnClick(Action action) => action();
}

public class MacTextBox : ITextBox
{
    private string _text = "";
    public void Render() => Console.WriteLine($"(Mac TextBox: {_text})");
    public string GetText() => _text;
    public void SetText(string text) => _text = text;
}

public class MacCheckBox : ICheckBox
{
    public void Render() => Console.WriteLine($"(Mac CheckBox: {IsChecked})");
    public bool IsChecked { get; set; }
}

public class MacUIFactory : IUIFactory
{
    public IButton CreateButton() => new MacButton();
    public ITextBox CreateTextBox() => new MacTextBox();
    public ICheckBox CreateCheckBox() => new MacCheckBox();
}

// Client code works with any factory
public class LoginForm
{
    private readonly IButton _loginButton;
    private readonly ITextBox _usernameField;
    private readonly ITextBox _passwordField;
    private readonly ICheckBox _rememberMe;

    public LoginForm(IUIFactory factory)
    {
        _loginButton = factory.CreateButton();
        _usernameField = factory.CreateTextBox();
        _passwordField = factory.CreateTextBox();
        _rememberMe = factory.CreateCheckBox();
    }

    public void Render()
    {
        _usernameField.Render();
        _passwordField.Render();
        _rememberMe.Render();
        _loginButton.Render();
    }
}

// Usage
IUIFactory factory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
    ? new WindowsUIFactory()
    : new MacUIFactory();

var loginForm = new LoginForm(factory);
loginForm.Render();

Factory with Dependency Injection

// Payment processor factory
public interface IPaymentProcessor
{
    Task<PaymentResult> ProcessAsync(decimal amount);
}

public class CreditCardProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} via credit card");
        return new PaymentResult { Success = true };
    }
}

public class PayPalProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} via PayPal");
        return new PaymentResult { Success = true };
    }
}

public class CryptoProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} via cryptocurrency");
        return new PaymentResult { Success = true };
    }
}

// Factory interface for DI
public interface IPaymentProcessorFactory
{
    IPaymentProcessor Create(string paymentMethod);
}

public class PaymentProcessorFactory : IPaymentProcessorFactory
{
    private readonly IServiceProvider _serviceProvider;

    public PaymentProcessorFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IPaymentProcessor Create(string paymentMethod)
    {
        return paymentMethod.ToLower() switch
        {
            "creditcard" => _serviceProvider.GetRequiredService<CreditCardProcessor>(),
            "paypal" => _serviceProvider.GetRequiredService<PayPalProcessor>(),
            "crypto" => _serviceProvider.GetRequiredService<CryptoProcessor>(),
            _ => throw new ArgumentException($"Unknown payment method: {paymentMethod}")
        };
    }
}

// Registration
services.AddTransient<CreditCardProcessor>();
services.AddTransient<PayPalProcessor>();
services.AddTransient<CryptoProcessor>();
services.AddSingleton<IPaymentProcessorFactory, PaymentProcessorFactory>();

// Usage in service
public class CheckoutService
{
    private readonly IPaymentProcessorFactory _factory;

    public CheckoutService(IPaymentProcessorFactory factory)
    {
        _factory = factory;
    }

    public async Task<PaymentResult> ProcessPaymentAsync(
        string paymentMethod,
        decimal amount)
    {
        var processor = _factory.Create(paymentMethod);
        return await processor.ProcessAsync(amount);
    }
}

Real-World Example: Report Generator

public interface IReport
{
    byte[] Generate(ReportData data);
    string ContentType { get; }
    string FileExtension { get; }
}

public class PdfReport : IReport
{
    public string ContentType => "application/pdf";
    public string FileExtension => ".pdf";

    public byte[] Generate(ReportData data)
    {
        // PDF generation logic
        Console.WriteLine("Generating PDF report...");
        return Array.Empty<byte>();
    }
}

public class ExcelReport : IReport
{
    public string ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    public string FileExtension => ".xlsx";

    public byte[] Generate(ReportData data)
    {
        // Excel generation logic
        Console.WriteLine("Generating Excel report...");
        return Array.Empty<byte>();
    }
}

public class CsvReport : IReport
{
    public string ContentType => "text/csv";
    public string FileExtension => ".csv";

    public byte[] Generate(ReportData data)
    {
        // CSV generation logic
        Console.WriteLine("Generating CSV report...");
        return Array.Empty<byte>();
    }
}

public class ReportFactory
{
    private readonly Dictionary<string, Func<IReport>> _factories;

    public ReportFactory()
    {
        _factories = new Dictionary<string, Func<IReport>>(StringComparer.OrdinalIgnoreCase)
        {
            ["pdf"] = () => new PdfReport(),
            ["excel"] = () => new ExcelReport(),
            ["csv"] = () => new CsvReport()
        };
    }

    public IReport Create(string format)
    {
        if (!_factories.TryGetValue(format, out var factory))
            throw new ArgumentException($"Unsupported format: {format}");

        return factory();
    }

    public IEnumerable<string> SupportedFormats => _factories.Keys;
}

// Usage
var factory = new ReportFactory();
var report = factory.Create("pdf");
var data = new ReportData { /* ... */ };
var bytes = report.Generate(data);

When to Use Which Factory

Pattern Use When
Simple Factory Just need centralized creation logic
Factory Method Subclasses should decide what to create
Abstract Factory Need families of related objects

Interview Tips

Common Questions:

  • โ€œExplain Factory pattern with an exampleโ€
  • โ€œWhatโ€™s the difference between Factory Method and Abstract Factory?โ€
  • โ€œWhen would you use Factory vs Dependency Injection?โ€

Key Points:

  1. Factory encapsulates object creation
  2. Factory Method uses inheritance
  3. Abstract Factory uses composition
  4. Combine with DI for best results
  5. .NET examples: ILoggerFactory, Task.Factory

Comparison:

  • Simple Factory: Static method, good for simple cases
  • Factory Method: Virtual method, subclasses override
  • Abstract Factory: Interface with multiple creation methods for related products

๐Ÿ“š Related Articles