Factory Method

The Factory Method is a creational design pattern that provides an interface for creating objects without specifying their exact classes. Instead of calling a constructor directly, you call a factory method that returns an instance of the appropriate class based on the given parameters or conditions.

Core Concept

The Factory Method pattern solves the problem of creating objects without having to specify the exact class of object that will be created. It defines a method for creating objects, but lets subclasses decide which class to instantiate. This promotes loose coupling by eliminating the need to bind application-specific classes into the code.

When to Use Factory Method

The Factory Method pattern is particularly useful when:

  • You need to delegate object creation to subclasses
  • The exact type of object to create is determined at runtime
  • You want to localize the knowledge of which class gets created
  • You need to provide a library of products and reveal only their interfaces
  • A class can’t anticipate the class of objects it must create

Structure and Components

    Creator (Abstract)
    ┌─────────────────────┐
    │ + factory_method()  │ ← Abstract method
    │ + some_operation()  │ ← Uses factory_method()
    └─────────────────────┘
             ▲
             │ inherits
             │
    ConcreteCreator
    ┌─────────────────────┐
    │ + factory_method()  │ ← Implements the method
    └─────────────────────┘
             │ creates
             ▼
       Product Interface
    ┌─────────────────────┐
    │ + operation()       │
    └─────────────────────┘
             ▲
             │ implements
             │
      ConcreteProduct
    ┌─────────────────────┐
    │ + operation()       │
    └─────────────────────┘

The pattern consists of four main components:

  1. Product Interface: Defines the interface for objects that the factory method creates
  2. Concrete Products: Implement the product interface
  3. Creator (Abstract): Declares the factory method and may provide a default implementation
  4. Concrete Creators: Override the factory method to return specific product instances

Benefits

  • Loose Coupling: Eliminates the need to bind application-specific classes
  • Single Responsibility: Centralizes object creation logic
  • Open/Closed Principle: Easy to add new product types without modifying existing code
  • Flexibility: Subclasses can override the type of objects created

Drawbacks

  • Increased Complexity: Requires creating additional classes and interfaces
  • Inheritance Dependency: Relies heavily on inheritance, which can be limiting
  • Potential Over-engineering: May be overkill for simple object creation scenarios## Code Example Explanation

This example demonstrates the Factory Method pattern through a document processing system. Here’s how each component works:

from abc import ABC, abstractmethod
from typing import Protocol, Dict, Any
from enum import Enum

# Product Interface - Defines what all document processors must implement
class DocumentProcessor(Protocol):
    """Protocol defining the interface for document processors."""
    
    def process(self, content: str) -> Dict[str, Any]:
        """Process the document content and return structured data."""
        ...
    
    def get_supported_format(self) -> str:
        """Return the supported document format."""
        ...

class WordProcessor:
    """Concrete processor for Word documents."""
    
    def process(self, content: str) -> Dict[str, Any]:
        """Process Word document content."""
        # In real implementation, this would use Word processing libraries
        return {
            "type": "WORD",
            "content": f"Processed Word content: {content[:50]}...",
            "word_count": len(content.split()),
            "metadata": {"format": "docx", "processed": True}
        }
    
    def get_supported_format(self) -> str:
        return "WORD"

class TextProcessor:
    """Concrete processor for plain text documents."""
    
    def process(self, content: str) -> Dict[str, Any]:
        """Process plain text content."""
        return {
            "type": "TEXT",
            "content": f"Processed text content: {content[:50]}...",
            "line_count": content.count("\\n") + 1,
            "metadata": {"format": "txt", "processed": True}
        }
    
    def get_supported_format(self) -> str:
        return "TEXT"

# Document types enumeration for better type safety
class DocumentType(Enum):
    PDF = "pdf"
    WORD = "docx"
    TEXT = "txt"

# Abstract Creator - Defines the factory method interface
class DocumentProcessorFactory(ABC):
    """Abstract factory for creating document processors."""
    
    @abstractmethod
    def create_processor(self) -> DocumentProcessor:
        """Factory method to create a document processor.
        
        This method must be implemented by concrete factories.
        """
        pass
    
    def process_document(self, content: str) -> Dict[str, Any]:
        """Template method that uses the factory method.
        
        This demonstrates how the factory method is used within
        the creator class to perform operations.
        """
        # Create the appropriate processor using factory method
        processor = self.create_processor()
        
        # Use the processor to handle the document
        result = processor.process(content)
        
        # Add some common processing metadata
        result["processor_type"] = processor.get_supported_format()
        result["factory_used"] = self.__class__.__name__
        
        return result

# Concrete Creators - Implement the factory method for specific document types
class PDFProcessorFactory(DocumentProcessorFactory):
    """Factory for creating PDF processors."""
    
    def create_processor(self) -> DocumentProcessor:
        """Create and return a PDF processor instance."""
        return PDFProcessor()

class WordProcessorFactory(DocumentProcessorFactory):
    """Factory for creating Word document processors."""
    
    def create_processor(self) -> DocumentProcessor:
        """Create and return a Word processor instance."""
        return WordProcessor()

class TextProcessorFactory(DocumentProcessorFactory):
    """Factory for creating text processors."""
    
    def create_processor(self) -> DocumentProcessor:
        """Create and return a text processor instance."""
        return TextProcessor()

# Factory Registry - Manages factory instances (Optional enhancement)
class ProcessorFactoryRegistry:
    """Registry to manage and retrieve appropriate factories."""
    
    def __init__(self) -> None:
        self._factories: Dict[DocumentType, DocumentProcessorFactory] = {}
        self._register_default_factories()
    
    def _register_default_factories(self) -> None:
        """Register default factories for standard document types."""
        self._factories[DocumentType.PDF] = PDFProcessorFactory()
        self._factories[DocumentType.WORD] = WordProcessorFactory()
        self._factories[DocumentType.TEXT] = TextProcessorFactory()
    
    def register_factory(self, doc_type: DocumentType, 
                        factory: DocumentProcessorFactory) -> None:
        """Register a new factory for a document type."""
        self._factories[doc_type] = factory
    
    def get_factory(self, doc_type: DocumentType) -> DocumentProcessorFactory:
        """Get the appropriate factory for a document type."""
        if doc_type not in self._factories:
            raise ValueError(f"No factory registered for document type: {doc_type}")
        return self._factories[doc_type]
    
    def process_document(self, doc_type: DocumentType, 
                        content: str) -> Dict[str, Any]:
        """Process a document using the appropriate factory."""
        factory = self.get_factory(doc_type)
        return factory.process_document(content)

# Client code demonstration
def demonstrate_factory_method():
    """Demonstrate the Factory Method pattern usage."""
    
    print("=== Factory Method Pattern Demonstration ===\\n")
    
    # Sample document content
    sample_content = {
        DocumentType.PDF: "PDF document content with \\page breaks and formatting",
        DocumentType.WORD: "Word document with rich formatting and embedded objects",
        DocumentType.TEXT: "Simple plain text document\\nwith multiple lines"
    }
    
    # Method 1: Direct factory usage
    print("1. Direct Factory Usage:")
    print("-" * 40)
    
    factories = [
        PDFProcessorFactory(),
        WordProcessorFactory(),
        TextProcessorFactory()
    ]
    
    for factory in factories:
        doc_type = DocumentType.PDF if isinstance(factory, PDFProcessorFactory) else \\
                  DocumentType.WORD if isinstance(factory, WordProcessorFactory) else \\
                  DocumentType.TEXT
        
        result = factory.process_document(sample_content[doc_type])
        print(f"Factory: {factory.__class__.__name__}")
        print(f"Result: {result['type']} - {result['content']}")
        print(f"Metadata: {result['metadata']}\\n")
    
    # Method 2: Using Factory Registry
    print("2. Using Factory Registry:")
    print("-" * 40)
    
    registry = ProcessorFactoryRegistry()
    
    for doc_type in DocumentType:
        result = registry.process_document(doc_type, sample_content[doc_type])
        print(f"Document Type: {doc_type.value}")
        print(f"Processed by: {result['processor_type']}")
        print(f"Content: {result['content']}")
        print(f"Factory Used: {result['factory_used']}\\n")

if __name__ == "__main__":
    demonstrate_factory_method()
  • Product Interface (DocumentProcessor Protocol): Defines the contract that all document processors must follow, ensuring they implement process()and get_supported_format()methods.
  • Concrete Products: Three different processors (PDFProcessor, WordProcessor, TextProcessor) each handle their specific document format while adhering to the same interface.
  • Abstract Creator (DocumentProcessorFactory): Defines the factory method create_processor() as abstract, forcing subclasses to implement it. It also provides a template method process_document(), that uses the factory method.
  • Concrete Creators: Each factory class implements the factory method to return the appropriate processor type. This is where the “factory” decision is made.
  • Registry Enhancement: The ProcessorFactoryRegistry demonstrates a practical extension where factories are managed centrally, making it easy to add new document types without modifying existing client code.

Track your progress

Mark this subtopic as completed when you finish reading.