Skip to main content

Command Palette

Search for a command to run...

Factory Pattern: Create Objects Flexibly

Learn: Factory Pattern: Create Objects Flexibly

Updated
4 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

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

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

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

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

AspectBenefit
DecouplingClient code independent of concrete classes
MaintainabilityChanges isolated to factory implementation
ExtensibilityAdd new types without modifying existing code
TestabilityEasy to mock and substitute implementations
FlexibilityRuntime 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.