# Factory Pattern: Create Objects Flexibly

# Factory Pattern: Create Objects Flexibly

## Problem

When building applications, you often need to create objects of different types based on runtime conditions. Hardcoding object instantiation throughout your codebase creates several issues:

- **Tight Coupling**: Code depends directly on concrete classes, making it difficult to swap implementations
- **Maintenance Burden**: Adding new object types requires changes in multiple places
- **Violation of Open/Closed Principle**: Classes should be open for extension but closed for modification
- **Complex Conditional Logic**: Scattered `if-else` or `switch` statements make code harder to read and maintain
- **Difficult Testing**: Hard to mock or substitute implementations for unit tests

For example, imagine a payment processing system that needs to handle credit cards, PayPal, and cryptocurrency. Without the Factory Pattern, you'd have payment creation logic scattered throughout your application, making it fragile and difficult to extend.

## Solution

The Factory Pattern provides a centralized mechanism for object creation. Instead of directly instantiating classes, you delegate creation to a factory—a dedicated component responsible for producing objects based on specified parameters.

**Key Benefits:**

- **Decoupling**: Client code doesn't depend on concrete implementations
- **Centralized Creation Logic**: All object instantiation happens in one place
- **Easy Extension**: Add new types without modifying existing code
- **Flexibility**: Switch implementations by changing factory logic, not client code
- **Testability**: Mock factories for unit testing

**Types of Factory Pattern:**

1. **Simple Factory**: A single factory method that creates objects
2. **Factory Method**: Subclasses decide which class to instantiate
3. **Abstract Factory**: Creates families of related objects

## Code

### Simple Factory Example: Payment Processing

```python
from abc import ABC, abstractmethod
from enum import Enum

# Abstract Product
class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass
    
    @abstractmethod
    def get_processor_name(self) -> str:
        pass

# Concrete Products
class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing ${amount} via Credit Card")
        return True
    
    def get_processor_name(self) -> str:
        return "Credit Card"

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing ${amount} via PayPal")
        return True
    
    def get_processor_name(self) -> str:
        return "PayPal"

class CryptoProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing ${amount} via Cryptocurrency")
        return True
    
    def get_processor_name(self) -> str:
        return "Cryptocurrency"

# Factory
class PaymentProcessorFactory:
    _processors = {
        'credit_card': CreditCardProcessor,
        'paypal': PayPalProcessor,
        'crypto': CryptoProcessor,
    }
    
    @staticmethod
    def create_processor(payment_type: str) -> PaymentProcessor:
        processor_class = PaymentProcessorFactory._processors.get(
            payment_type.lower()
        )
        
        if not processor_class:
            raise ValueError(
                f"Unknown payment type: {payment_type}. "
                f"Available: {list(PaymentProcessorFactory._processors.keys())}"
            )
        
        return processor_class()
    
    @staticmethod
    def register_processor(payment_type: str, processor_class):
        """Allow runtime registration of new processors"""
        PaymentProcessorFactory._processors[payment_type.lower()] = processor_class

# Client Code
def process_order(payment_type: str, amount: float):
    try:
        processor = PaymentProcessorFactory.create_processor(payment_type)
        success = processor.process_payment(amount)
        print(f"✓ {processor.get_processor_name()} payment successful\n")
        return success
    except ValueError as e:
        print(f"✗ Error: {e}\n")
        return False

# Usage
if __name__ == "__main__":
    process_order('credit_card', 99.99)
    process_order('paypal', 49.99)
    process_order('crypto', 0.5)
    process_order('bitcoin', 100)  # Error handling
```

### Factory Method Example: Document Generation

```python
from abc import ABC, abstractmethod

# Abstract Product
class Document(ABC):
    @abstractmethod
    def open(self):
        pass
    
    @abstractmethod
    def save(self):
        pass
    
    @abstractmethod
    def close(self):
        pass

# Concrete Products
class PDFDocument(Document):
    def open(self):
        print("Opening PDF document...")
    
    def save(self):
        print("Saving as PDF...")
    
    def close(self):
        print("Closing PDF document...")

class WordDocument(Document):
    def open(self):
        print("Opening Word document...")
    
    def save(self):
        print("Saving as DOCX...")
    
    def close(self):
        print("Closing Word document...")

class ExcelDocument(Document):
    def open(self):
        print("Opening Excel spreadsheet...")
    
    def save(self):
        print("Saving as XLSX...")
    
    def close(self):
        print("Closing Excel spreadsheet...")

# Abstract Creator
class DocumentCreator(ABC):
    @abstractmethod
    def create_document(self) -> Document:
        pass
    
    def new_document(self):
        doc = self.create_document()
        doc.open()
        return doc

# Concrete Creators
class PDFCreator(DocumentCreator):
    def create_document(self) -> Document:
        return PDFDocument()

class WordCreator(DocumentCreator):
    def create_document(self) -> Document:
        return WordDocument()

class ExcelCreator(DocumentCreator):
    def create_document(self) -> Document:
        return ExcelDocument()

# Usage
def create_and_process_document(creator: DocumentCreator):
    doc = creator.new_document()
    doc.save()
    doc.close()

if __name__ == "__main__":
    create_and_process_document(PDFCreator())
    print()
    create_and_process_document(WordCreator())
    print()
    create_and_process_document(ExcelCreator())
```

### Abstract Factory Example: UI Theme System

```python
from abc import ABC, abstractmethod

# Abstract Products
class Button(ABC):
    @abstractmethod
    def render(self):
        pass

class Checkbox(ABC):
    @abstractmethod
    def render(self):
        pass

# Concrete Products - Light Theme
class LightButton(Button):
    def render(self):
        return "🔘 Light Button (white background)"

class LightCheckbox(Checkbox):
    def render(self):
        return "☐ Light Checkbox (light gray)"

# Concrete Products - Dark Theme
class DarkButton(Button):
    def render(self):
        return "🔘 Dark Button (black background)"

class DarkCheckbox(Checkbox):
    def render(self):
        return "☑ Dark Checkbox (dark gray)"

# Abstract Factory
class UIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass
    
    @abstractmethod
    def create_checkbox(self) -> Checkbox:
        pass

# Concrete Factories
class LightThemeFactory(UIFactory):
    def create_button(self) -> Button:
        return LightButton()
    
    def create_checkbox(self) -> Checkbox:
        return LightCheckbox()

class DarkThemeFactory(UIFactory):
    def create_button(self) -> Button:
        return DarkButton()
    
    def create_checkbox(self) -> Checkbox:
        return DarkCheckbox()

# Client Code
class Application:
    def __init__(self, factory: UIFactory):
        self.factory = factory
    
    def render_ui(self):
        button = self.factory.create_button()
        checkbox = self.factory.create_checkbox()
        print(button.render())
        print(checkbox.render())

if __name__ == "__main__":
    print("Light Theme:")
    app = Application(LightThemeFactory())
    app.render_ui()
    
    print("\nDark Theme:")
    app = Application(DarkThemeFactory())
    app.render_ui()
```

## Key Takeaways

| Aspect | Benefit |
|--------|---------|
| **Decoupling** | Client code independent of concrete classes |
| **Maintainability** | Changes isolated to factory implementation |
| **Extensibility** | Add new types without modifying existing code |
| **Testability** | Easy to mock and substitute implementations |
| **Flexibility** | Runtime object creation based on conditions |

The Factory Pattern is essential for building scalable, maintainable applications where object creation logic needs to be centralized and flexible.
