Factory Patterns
Factory patterns encapsulate object creation, hiding the instantiation logic from client code. There are several variations:
- Simple Factory - A method that creates objects
- Factory Method - Subclasses decide which class to instantiate
- 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:
- Factory encapsulates object creation
- Factory Method uses inheritance
- Abstract Factory uses composition
- Combine with DI for best results
- .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