Factory Method
Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
Factory Pattern: A Complete Guide with Examples in 11 Programming Languages
The Factory Pattern is one of the most widely used creational design patterns across all modern programming languages, helping developers write cleaner, more maintainable code by delegating object creation to specialized factory classes. Whether you're building a payment processing system, a document generator, or a notification service, understanding the Factory Pattern will make your code more flexible and easier to extend.
In this comprehensive guide, we'll explore the Factory Method Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin - complete with language-specific best practices and common pitfalls.
Table of Contents
- What is the Factory Pattern?
- Why Use the Factory Pattern?
- Factory Pattern Types: Quick Comparison
- Factory Method Pattern Explained
- Class Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the Factory Pattern?
The Factory Pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. Instead of calling constructors directly, you delegate object creation to a factory method.
Think of it like ordering food at a restaurant. You don't go into the kitchen and cook the meal yourself. You tell the waiter what you want, and the kitchen (the factory) creates it for you. You get your meal without needing to know the recipe or cooking process.
Why Use the Factory Pattern?
The Factory Pattern offers several compelling benefits across all programming languages:
- Decoupling: Your code doesn't need to know the concrete classes it's working with, only the interface
- Flexibility: Adding new product types doesn't require changing existing code
- Single Responsibility: Object creation logic is centralized in one place
- Testability: Easier to mock and test since dependencies are injected through factories
- Maintainability: Changes to object creation are isolated to factory classes
Factory Pattern Types: Quick Comparison
Before diving deep into Factory Method, let's understand how it compares to related patterns:
| Pattern | Complexity | When to Use | Key Characteristic |
|---|---|---|---|
| Simple Factory | Low | Single factory creates all products | Not a true pattern; static method returns objects |
| Factory Method | Medium | Each creator subclass decides which product to create | Uses inheritance; subclasses override creation method |
| Abstract Factory | High | Need to create families of related objects | Multiple factories creating multiple related products |
Today's focus: Factory Method Pattern - the sweet spot between simplicity and flexibility.
Factory Method Pattern Explained
The Factory Method Pattern defines an interface for creating an object but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses.
Core Components:
- Product Interface: Defines the interface of objects the factory creates
- Concrete Products: Specific implementations of the product interface
- Creator (Abstract Factory): Declares the factory method returning Product objects
- Concrete Creators: Override the factory method to return specific product instances
Class Diagram
Here's the UML class diagram showing the Factory Method structure:
Beginner-Friendly Example
Let's start with a simple, easy-to-understand example: a notification system that can send different types of notifications. Below you'll find implementations in all 11 languages.
from abc import ABC, abstractmethod
# Product Interface
class Notification(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
# Concrete Products
class EmailNotification(Notification):
def send(self, message: str) -> None:
print(f"š§ Sending Email: {message}")
class SMSNotification(Notification):
def send(self, message: str) -> None:
print(f"š± Sending SMS: {message}")
class PushNotification(Notification):
def send(self, message: str) -> None:
print(f"š Sending Push Notification: {message}")
# Creator (Abstract Factory)
class NotificationCreator(ABC):
@abstractmethod
def create_notification(self) -> Notification:
pass
def notify(self, message: str) -> None:
notification = self.create_notification()
notification.send(message)
# Concrete Creators
class EmailNotificationCreator(NotificationCreator):
def create_notification(self) -> Notification:
return EmailNotification()
class SMSNotificationCreator(NotificationCreator):
def create_notification(self) -> Notification:
return SMSNotification()
class PushNotificationCreator(NotificationCreator):
def create_notification(self) -> Notification:
return PushNotification()
# Usage
if __name__ == "__main__":
email_creator = EmailNotificationCreator()
email_creator.notify("Your order has been shipped!")
sms_creator = SMSNotificationCreator()
sms_creator.notify("Your verification code is 123456")
Production-Ready Example
Now let's look at a more realistic, production-ready implementation: a payment processing system that handles different payment methods with proper error handling, logging, and configuration. I'll show this in Python first, but the same patterns apply across all languages.
š Python (Production Example)
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Optional
from enum import Enum
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Enums and Data Classes
class PaymentStatus(Enum):
SUCCESS = "success"
FAILED = "failed"
PENDING = "pending"
@dataclass
class PaymentResult:
status: PaymentStatus
transaction_id: str
amount: float
timestamp: datetime
message: str
metadata: Optional[Dict] = None
# Product Interface
class PaymentProcessor(ABC):
"""Abstract base class for payment processors"""
def __init__(self, config: Dict):
self.config = config
self.validate_config()
@abstractmethod
def validate_config(self) -> None:
"""Validate processor-specific configuration"""
pass
@abstractmethod
def process_payment(self, amount: float, currency: str, **kwargs) -> PaymentResult:
"""Process a payment and return the result"""
pass
@abstractmethod
def refund(self, transaction_id: str) -> PaymentResult:
"""Process a refund"""
pass
# Concrete Products
class StripePaymentProcessor(PaymentProcessor):
def validate_config(self) -> None:
required_keys = ['api_key', 'webhook_secret']
for key in required_keys:
if key not in self.config:
raise ValueError(f"Missing required config: {key}")
def process_payment(self, amount: float, currency: str, **kwargs) -> PaymentResult:
try:
logger.info(f"Processing Stripe payment: ${amount} {currency}")
customer_id = kwargs.get('customer_id')
card_token = kwargs.get('card_token')
if not customer_id or not card_token:
raise ValueError("Missing customer_id or card_token")
# In real implementation, this would call Stripe's API
transaction_id = f"stripe_{datetime.now().timestamp()}"
logger.info(f"Stripe payment successful: {transaction_id}")
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=transaction_id,
amount=amount,
timestamp=datetime.now(),
message="Payment processed successfully via Stripe",
metadata={'processor': 'stripe', 'customer_id': customer_id}
)
except Exception as e:
logger.error(f"Stripe payment failed: {str(e)}")
return PaymentResult(
status=PaymentStatus.FAILED,
transaction_id="",
amount=amount,
timestamp=datetime.now(),
message=f"Payment failed: {str(e)}"
)
def refund(self, transaction_id: str) -> PaymentResult:
logger.info(f"Processing Stripe refund for: {transaction_id}")
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=f"refund_{transaction_id}",
amount=0.0,
timestamp=datetime.now(),
message="Refund processed successfully"
)
class PayPalPaymentProcessor(PaymentProcessor):
def validate_config(self) -> None:
required_keys = ['client_id', 'client_secret']
for key in required_keys:
if key not in self.config:
raise ValueError(f"Missing required config: {key}")
def process_payment(self, amount: float, currency: str, **kwargs) -> PaymentResult:
try:
logger.info(f"Processing PayPal payment: ${amount} {currency}")
email = kwargs.get('email')
if not email:
raise ValueError("Missing PayPal email")
transaction_id = f"paypal_{datetime.now().timestamp()}"
logger.info(f"PayPal payment successful: {transaction_id}")
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=transaction_id,
amount=amount,
timestamp=datetime.now(),
message="Payment processed successfully via PayPal",
metadata={'processor': 'paypal', 'email': email}
)
except Exception as e:
logger.error(f"PayPal payment failed: {str(e)}")
return PaymentResult(
status=PaymentStatus.FAILED,
transaction_id="",
amount=amount,
timestamp=datetime.now(),
message=f"Payment failed: {str(e)}"
)
def refund(self, transaction_id: str) -> PaymentResult:
logger.info(f"Processing PayPal refund for: {transaction_id}")
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=f"refund_{transaction_id}",
amount=0.0,
timestamp=datetime.now(),
message="Refund processed successfully"
)
# Creator (Abstract Factory)
class PaymentProcessorFactory(ABC):
"""Abstract factory for creating payment processors"""
@abstractmethod
def create_processor(self) -> PaymentProcessor:
pass
def process_transaction(self, amount: float, currency: str, **kwargs) -> PaymentResult:
"""High-level method that uses the factory method"""
processor = self.create_processor()
return processor.process_payment(amount, currency, **kwargs)
# Concrete Creators
class StripeProcessorFactory(PaymentProcessorFactory):
def __init__(self, api_key: str, webhook_secret: str):
self.config = {
'api_key': api_key,
'webhook_secret': webhook_secret
}
def create_processor(self) -> PaymentProcessor:
return StripePaymentProcessor(self.config)
class PayPalProcessorFactory(PaymentProcessorFactory):
def __init__(self, client_id: str, client_secret: str):
self.config = {
'client_id': client_id,
'client_secret': client_secret
}
def create_processor(self) -> PaymentProcessor:
return PayPalPaymentProcessor(self.config)
# Client code - Payment Service
class PaymentService:
"""High-level service that uses payment factories"""
def __init__(self):
self.factories: Dict[str, PaymentProcessorFactory] = {}
def register_factory(self, payment_method: str, factory: PaymentProcessorFactory):
"""Register a payment processor factory"""
self.factories[payment_method] = factory
logger.info(f"Registered payment method: {payment_method}")
def charge(self, payment_method: str, amount: float, currency: str, **kwargs) -> PaymentResult:
"""Process a payment using the specified method"""
if payment_method not in self.factories:
raise ValueError(f"Unknown payment method: {payment_method}")
factory = self.factories[payment_method]
return factory.process_transaction(amount, currency, **kwargs)
# Usage Example
def main():
# Initialize payment service
payment_service = PaymentService()
# Register payment methods
payment_service.register_factory(
'stripe',
StripeProcessorFactory(
api_key='sk_test_xxxxx',
webhook_secret='whsec_xxxxx'
)
)
payment_service.register_factory(
'paypal',
PayPalProcessorFactory(
client_id='paypal_client_id',
client_secret='paypal_secret'
)
)
# Process payments
result1 = payment_service.charge(
'stripe',
99.99,
'USD',
customer_id='cus_123',
card_token='tok_visa'
)
print(f"Stripe: {result1.status.value} - {result1.message}")
result2 = payment_service.charge(
'paypal',
149.99,
'USD',
email='customer@example.com'
)
print(f"PayPal: {result2.status.value} - {result2.message}")
if __name__ == "__main__":
main()
Key Production Features:
- Error Handling: Try-catch blocks with proper logging
- Configuration Validation: Each processor validates its config on initialization
- Type Safety: Using type hints, dataclasses, and enums
- Extensibility: Easy to add new payment methods without modifying existing code
- Single Responsibility: Each class has one clear purpose
- Dependency Injection: Configuration is injected through constructors
- Logging: Comprehensive logging for debugging and monitoring
Real-World Use Cases
The Factory Method Pattern shines in many real-world scenarios across different domains:
1. Document Generation Systems
Generate different document formats (PDF, DOCX, HTML) based on user preferences:
DocumentFactory ā PDFDocument, WordDocument, HTMLDocument
Industries: Content management, reporting tools, document automation
2. Database Connection Management
Create connections to different databases (MySQL, PostgreSQL, MongoDB):
DatabaseFactory ā MySQLConnection, PostgresConnection, MongoConnection
Industries: ORMs, data analytics platforms, enterprise applications
3. GUI Component Libraries
Create UI components for different platforms (Windows, macOS, Linux):
UIComponentFactory ā WindowsButton, MacButton, LinuxButton
Industries: Cross-platform desktop apps, game engines, UI frameworks
4. Logging Systems
Route logs to different destinations (file, database, cloud):
LoggerFactory ā FileLogger, DatabaseLogger, CloudLogger
Industries: Enterprise software, microservices, observability platforms
5. Payment Processing
Handle different payment gateways (Stripe, PayPal, Square):
PaymentFactory ā StripeProcessor, PayPalProcessor, SquareProcessor
Industries: E-commerce, fintech, SaaS platforms
6. Notification Services
Send notifications through various channels (email, SMS, push, Slack):
NotificationFactory ā EmailNotification, SMSNotification, PushNotification
Industries: Marketing automation, alerting systems, communication platforms
7. API Client Libraries
Create clients for different API versions or protocols:
APIClientFactory ā RESTClient, GraphQLClient, gRPCClient
Industries: Integration platforms, API gateways, microservices
8. Cloud Provider Abstraction
Abstract cloud services across providers (AWS, Azure, GCP):
CloudStorageFactory ā S3Storage, AzureBlobStorage, GCSStorage
Industries: Multi-cloud platforms, infrastructure automation, DevOps tools
Language-Specific Mistakes and Anti-Patterns
Different programming languages have their own idioms and pitfalls when implementing the Factory Pattern. Let's explore common mistakes in each language:
ā Mistake #1: Not Using Abstract Base Classes
# BAD: No enforcement of interface
class NotificationCreator:
def create_notification(self):
pass # Subclasses might forget to implement
# GOOD: Use ABC
from abc import ABC, abstractmethod
class NotificationCreator(ABC):
@abstractmethod
def create_notification(self) -> Notification:
pass # Forces subclasses to implement
ā Mistake #2: Ignoring Type Hints
# BAD: No type hints
def create_notification():
return EmailNotification()
# GOOD: Clear return types
def create_notification(self) -> Notification:
return EmailNotification()
ā Mistake #3: Mutable Default Arguments
# BAD: Mutable default can cause bugs
class Factory:
def __init__(self, config={}):
self.config = config # Shared across instances!
# GOOD: Use None and create new dict
class Factory:
def __init__(self, config: Optional[Dict] = None):
self.config = config or {}
Frequently Asked Questions
Q1: What's the difference between Factory Method and Abstract Factory?
Factory Method creates one product type using inheritance. Each factory subclass creates a different product variant.
Abstract Factory creates families of related products. One factory creates multiple related products (e.g., Windows UI factory creates WinButton, WinDialog, WinMenu).
Use Factory Method when: You need to create one type of object with different implementations.
Use Abstract Factory when: You need to create multiple related objects that work together.
Q2: Should I always use abstract classes/interfaces for factories?
Not necessarily! It depends on your language and use case:
- Python/JavaScript: Can use duck typing for simple cases
- Java/C#/TypeScript: Interfaces/abstract classes provide compile-time safety
- Go: Interfaces are implicit, so products naturally satisfy them
- Rust: Traits are the way to go for polymorphism
Use abstractions when you have multiple implementations or expect to add more later.
Q3: How does Factory Pattern work with dependency injection?
They work great together! The factory can be injected as a dependency:
class PaymentService:
def __init__(self, factory: PaymentProcessorFactory):
self.factory = factory # Injected dependency
def process(self, amount: float):
processor = self.factory.create_processor()
return processor.process_payment(amount, "USD")
This combines flexible object creation with loose coupling.
Q4: Can factories return null/None/nil?
Yes, but handle it explicitly:
def create_processor(self) -> Optional[PaymentProcessor]:
try:
return StripePaymentProcessor(self.config)
except ConfigurationError:
return None # Caller must check
Or use Result/Option types in languages like Rust:
fn create() -> Result<Box<dyn Product>, Error> {
// ...
}
Q5: How do I test code that uses Factory Method?
Testing is easier with Factory Method! Use mock factories:
class MockPaymentFactory(PaymentProcessorFactory):
def create_processor(self) -> PaymentProcessor:
return MockPaymentProcessor() # Test double
def test_payment_service():
service = PaymentService(MockPaymentFactory())
result = service.charge(100.0, "USD")
assert result.status == PaymentStatus.SUCCESS
Q6: Should factories be singletons?
Usually no! Factories often need configuration, making them unsuitable for singleton pattern:
# Multiple factories with different configs
stripe_factory = StripeFactory(api_key="key1")
paypal_factory = PayPalFactory(client_id="id1")
Exception: Simple stateless factories can be singletons.
Q7: How do I handle async operations in factories?
Different languages handle this differently:
Python:
class AsyncFactory(ABC):
@abstractmethod
async def create(self) -> Product:
pass
TypeScript:
abstract class AsyncFactory {
abstract async create(): Promise<Product>;
}
C#:
public abstract class AsyncFactory {
public abstract Task<IProduct> CreateAsync();
}
Rust:
#[async_trait]
trait AsyncFactory {
async fn create(&self) -> Result<Box<dyn Product>>;
}
Q8: Can I combine Factory Pattern with other patterns?
Absolutely! Common combinations:
- Factory + Singleton: Factory itself is singleton (rare)
- Factory + Dependency Injection: Inject factories into services
- Factory + Strategy: Factory creates different strategies
- Factory + Builder: Factory returns builder for complex construction
- Factory + Registry: Register factories in a central registry
Q9: How do I version my factories?
Use namespaces, versioning in names, or separate modules:
# v1/factory.py
class PaymentFactoryV1: ...
# v2/factory.py
class PaymentFactoryV2: ...
# Or use configuration
factory = PaymentFactory(api_version="v2")
Q10: What about factories with many parameters?
Use the Builder pattern alongside Factory:
factory = (StripeFactory.builder()
.with_api_key("key")
.with_timeout(30)
.with_retry_count(3)
.build())
Or use configuration objects:
config = FactoryConfig(api_key="key", timeout=30)
factory = StripeFactory(config)
Key Takeaways
Let's wrap up what we've learned about the Factory Method Pattern across multiple programming languages:
šÆ Core Concept: Factory Method delegates object creation to subclasses, promoting loose coupling and extensibility. This principle remains the same whether you're writing Python, Java, TypeScript, or any other language.
š Universal Benefits:
- Follows the Open/Closed Principle - extend without modifying existing code
- Single Responsibility - creation logic is separated from business logic
- Better testability through dependency injection
- Code is more maintainable and easier to understand
- Works seamlessly across different programming paradigms
š Language-Specific Considerations:
- Statically-typed languages (Java, C#, TypeScript, Kotlin, Swift): Leverage interfaces, generics, and compile-time type safety
- Dynamically-typed languages (Python, JavaScript, PHP): Use duck typing carefully; consider type hints and validation
- Systems languages (Go, Rust): Focus on explicit error handling and memory safety
- Modern languages (Kotlin, Swift, Dart): Take advantage of language-specific features like sealed classes, extensions, and named constructors
š When to Use:
- You have multiple implementations of an interface
- You expect to add more implementations in the future
- Object creation involves complex logic or configuration
- You want to decouple client code from concrete classes
- You're building a library or framework that others will extend
ā ļø When NOT to Use:
- You only have one implementation with no plans for more
- Creation logic is trivial (just calling a constructor)
- The added abstraction outweighs the benefits
- You're in the prototyping phase and need to move fast
š ļø Best Practices Across All Languages:
- Keep factory methods simple and focused
- Use appropriate type systems for your language (interfaces, protocols, traits)
- Validate configuration early (constructor/initialization time)
- Include proper error handling and logging
- Prefer enums over strings for type identifiers
- Consider combining with dependency injection
- Write tests with mock factories
- Document what each factory creates and when to use it
š” Remember: The Factory Method Pattern isn't about making your code complex - it's about making it flexible, maintainable, and testable. The pattern looks slightly different in each language, but the core principles remain the same. Use it when it genuinely adds value, not just because it's a "design pattern."
Whether you're building a payment system in Python, a notification service in Java, a document generator in C#, or a cloud abstraction layer in Go, the Factory Method Pattern provides a solid foundation for extensible, maintainable code.
š Next Steps:
- Implement a simple factory in your primary language
- Refactor existing code that has complex object creation logic
- Explore the Abstract Factory pattern for creating families of objects
- Study how popular frameworks use factories (Spring in Java, Symfony in PHP, etc.)
- Practice identifying when NOT to use the pattern
Want to dive deeper into design patterns? Check out our comprehensive guides on:
- Abstract Factory Pattern
- Builder Pattern
- Singleton Pattern
- Prototype Pattern
- Dependency Injection Each guide includes examples in all 11 programming languages with language-specific best practices.