12 Factory Method Pattern
12.1 What is Factory Method?
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
The key idea: “Define an interface for creating an object, but let subclasses decide which class to instantiate.”
12.2 Structure (from your diagram)
Legend:
- ➊ Product Interface - defines what products can do
- ➋ Concrete Products - actual implementations
- ➌ Creator - declares factory method (abstract)
- ➍ Concrete Creators - implement factory method, decide WHICH product to create
12.3 Python Example: Medical Image Exporter
Here’s a practical example relevant to radiology—exporting medical images in different formats:
from abc import ABC, abstractmethod
# ═══════════════════════════════════════════════════════════════
# PRODUCT INTERFACE ➊
# ═══════════════════════════════════════════════════════════════
class ImageExporter(ABC):
"""Abstract Product: defines interface for all exporters."""
@abstractmethod
def export(self, image_data: bytes, filename: str) -> str:
"""Export image data to a file."""
pass
@abstractmethod
def get_extension(self) -> str:
"""Return file extension for this format."""
pass
# ═══════════════════════════════════════════════════════════════
# CONCRETE PRODUCTS ➋
# ═══════════════════════════════════════════════════════════════
class DicomExporter(ImageExporter):
"""Concrete Product: exports to DICOM format."""
def export(self, image_data: bytes, filename: str) -> str:
filepath = f"{filename}.{self.get_extension()}"
print(f"Exporting DICOM with metadata tags to: {filepath}")
# Actual DICOM writing logic here...
return filepath
def get_extension(self) -> str:
return "dcm"
class NiftiExporter(ImageExporter):
"""Concrete Product: exports to NIfTI format."""
def export(self, image_data: bytes, filename: str) -> str:
filepath = f"{filename}.{self.get_extension()}"
print(f"Exporting NIfTI with affine matrix to: {filepath}")
# Actual NIfTI writing logic here...
return filepath
def get_extension(self) -> str:
return "nii.gz"
class PngExporter(ImageExporter):
"""Concrete Product: exports to PNG format."""
def export(self, image_data: bytes, filename: str) -> str:
filepath = f"{filename}.{self.get_extension()}"
print(f"Exporting PNG (8-bit, window/level applied) to: {filepath}")
# Actual PNG writing logic here...
return filepath
def get_extension(self) -> str:
return "png"
# ═══════════════════════════════════════════════════════════════
# CREATOR (ABSTRACT) ➌
# ═══════════════════════════════════════════════════════════════
class ImageProcessor(ABC):
"""
Abstract Creator: declares the factory method.
Note: Creator's primary responsibility is NOT creating products.
It contains core business logic that USES products.
"""
@abstractmethod
def create_exporter(self) -> ImageExporter:
"""
Factory Method: subclasses decide which exporter to create.
"""
pass
def process_and_export(self, image_data: bytes, filename: str) -> str:
"""
Core business logic that uses the factory method.
This method doesn't know which concrete exporter it's using!
"""
# Step 1: Pre-processing (shared logic)
print("Preprocessing image...")
# Step 2: Get exporter via factory method
exporter = self.create_exporter() # <-- Factory Method call
# Step 3: Export using the product
result = exporter.export(image_data, filename)
# Step 4: Post-processing (shared logic)
print(f"Export complete: {result}\n")
return result
# ═══════════════════════════════════════════════════════════════
# CONCRETE CREATORS ➍
# ═══════════════════════════════════════════════════════════════
class DicomProcessor(ImageProcessor):
"""Concrete Creator: creates DICOM exporter."""
def create_exporter(self) -> ImageExporter:
return DicomExporter()
class NiftiProcessor(ImageProcessor):
"""Concrete Creator: creates NIfTI exporter."""
def create_exporter(self) -> ImageExporter:
return NiftiExporter()
class PngProcessor(ImageProcessor):
"""Concrete Creator: creates PNG exporter."""
def create_exporter(self) -> ImageExporter:
return PngExporter()
# ═══════════════════════════════════════════════════════════════
# CLIENT CODE
# ═══════════════════════════════════════════════════════════════
def client_code(processor: ImageProcessor, image_data: bytes, filename: str):
"""
Client works with any processor via the abstract interface.
It doesn't know which concrete product is being used!
"""
processor.process_and_export(image_data, filename)
# Usage
if __name__ == "__main__":
dummy_image = b"image_bytes_here"
# Client doesn't know about concrete exporters
client_code(DicomProcessor(), dummy_image, "brain_mri")
client_code(NiftiProcessor(), dummy_image, "brain_mri")
client_code(PngProcessor(), dummy_image, "brain_mri_slice")Output:
Preprocessing image...
Exporting DICOM with metadata tags to: brain_mri.dcm
Export complete: brain_mri.dcm
Preprocessing image...
Exporting NIfTI with affine matrix to: brain_mri.nii.gz
Export complete: brain_mri.nii.gz
Preprocessing image...
Exporting PNG (8-bit, window/level applied) to: brain_mri_slice.png
Export complete: brain_mri_slice.png
12.4 When to Use Factory Method?
Use Factory Method when:
┌─────────────────────────────────────────────────────────────┐
│ ✓ You don't know exact types of objects beforehand │
│ ✓ You want to let subclasses specify which objects │
│ to create │
│ ✓ You want to localize the knowledge of which class │
│ gets created │
│ ✓ You want to decouple client code from concrete classes │
└─────────────────────────────────────────────────────────────┘
12.5 Factory Method vs Simple Factory
Simple Factory (not a pattern, just a technique):
┌────────────────────────────────────────┐
│ class SimpleFactory: │
│ def create(type): │
│ if type == "A": return A() │
│ if type == "B": return B() │ <-- All logic in ONE place
│ │ (violates Open/Closed)
└────────────────────────────────────────┘
Factory Method Pattern:
┌────────────────────────────────────────┐
│ class CreatorA: │
│ def create(): return A() │
│ │
│ class CreatorB: │ <-- Each subclass handles
│ def create(): return B() │ its own creation
│ │ (Open for extension)
└────────────────────────────────────────┘
12.6 Key Benefits
- Open/Closed Principle: Add new product types without modifying existing code
- Single Responsibility: Product creation is in one place per type
- Loose Coupling: Client code depends on abstractions, not concrete classes
