Decoupling Creation: Implementing the Factory Pattern in Python

Overview of the Factory Pattern

In complex software systems, hardcoding object creation leads to rigid, fragile code. The

solves this by separating the process of object creation from the code that uses those objects. This separation is vital for maintaining high cohesion and low coupling. By shifting the responsibility of instantiation to a specialized factory, the main application logic remains agnostic to specific implementation details. If you need to swap a low-quality video codec for a high-quality one, you only change the factory, not the entire workflow.

Prerequisites

To follow this guide, you should possess a solid understanding of

fundamentals, specifically Object-Oriented Programming (OOP). Familiarity with Abstract Base Classes (ABCs) and the concept of Inheritance is required. You should also understand basic software design principles like Cohesion and Coupling.

Decoupling Creation: Implementing the Factory Pattern in Python
The Factory Pattern in Python // Separate Creation From Use

Key Libraries & Tools

  • abc: The built-in Python module for defining Abstract Base Classes.
  • pathlib: Used for object-oriented filesystem path manipulation.

Code Walkthrough

First, we define an abstract interface using the abc module. This ensures every factory provides the necessary creation methods.

from abc import ABC, abstractmethod

class ExporterFactory(ABC):
    @abstractmethod
    def get_video_exporter(self) -> VideoExporter:
        """Returns a new video exporter instance."""

    @abstractmethod
    def get_audio_exporter(self) -> AudioExporter:
        """Returns a new audio exporter instance."""

Next, we implement concrete factories. Each factory represents a specific configuration, such as a "Fast" or "Master" quality setup.

class FastExporter(ExporterFactory):
    def get_video_exporter(self) -> VideoExporter:
        return H264BaselineVideoExporter()
    
    def get_audio_exporter(self) -> AudioExporter:
        return AACAudioExporter()

Finally, we use Dependency Injection by passing the factory into the main function. This removes the need for the main() function to know about specific exporter classes.

def main(factory: ExporterFactory):
    video_exporter = factory.get_video_exporter()
    audio_exporter = factory.get_audio_exporter()
    # Proceed with export logic...

Syntax Notes

We use Type Hinting to specify that the main function expects an ExporterFactory type. This increases code readability and assists IDEs in providing better autocomplete. The use of the @abstractmethod decorator enforces that any subclass must implement the defined methods, preventing runtime errors caused by missing functionality.

Practical Examples

Factories shine in scenarios where objects must be grouped by environment or locale. Consider a Global Web Shop: a UKFactory might return a GBP currency object and a VAT tax calculator, while a USFactory returns USD and SalesTax objects. The checkout logic remains identical; only the factory changes.

Tips & Gotchas

Avoid the "Class Explosion" trap. If your application requires every possible combination of video and audio codecs (e.g., Low Video + High Audio), creating a factory for every permutation is inefficient. In those cases, favor Composition and Dependency Inversion over the static Factory Pattern to maintain flexibility without cluttering your codebase with dozens of subclasses.

3 min read