How to Use the Factory Pattern in Python

by SkillAiNest

Design patterns are proven solutions to common problems in software development. If you’ve ever found yourself repeatedly writing object creation code or struggling to manage different types of objects, Factory pattern It may be exactly what you need.

In this tutorial, you’ll learn what the factory pattern is, why it’s useful, and how to implement it in Python. We will prepare practical examples that show you when and how to use this pattern in real-world applications.

You can find the code On GitHub.

Conditions

Before we begin, make sure you have:

  • Python 3.10 or higher is installed

  • Understanding of Python classes and methods

  • Acquaintance with Object oriented programming (OOP) concepts

Let’s begin!

Table of Contents

What is the factory sample?

A factory pattern is a creative design pattern Provides an interface to objects without specifying their exact classes. Instead of calling a constructor directly, you call a factory method that decides which class to instantiate.

Think of it like ordering food at a restaurant. You don’t go into the kitchen and cook yourself. You tell the waiter what you want, and the kitchen (factory) creates it for you. You eat without worrying about the recipe or the cooking process.

A factory pattern is useful when:

  • You have multiple related classes and you need to decide which one to instantiate at runtime.

  • Object creation logic is complex and you want to encapsulate it

  • You want to make your code more maintainable and testable

A simple factory example

Let’s start with a basic example. Say you’re building a notification system that can send messages via email, SMS, or push notifications.

Without a factory, you can write code like this everywhere in your application:


if notification_type == "email":
    notifier = EmailNotifier()
elif notification_type == "sms":
    notifier = SMSNotifier()
elif notification_type == "push":
    notifier = PushNotifier()

It gets dirty quickly. Let’s use a factory instead:

class EmailNotifier:
    def send(self, message):
        return f"Sending email: {message}"

class SMSNotifier:
    def send(self, message):
        return f"Sending SMS: {message}"

class PushNotifier:
    def send(self, message):
        return f"Sending push notification: {message}"

class NotificationFactory:
    @staticmethod
    def create_notifier(notifier_type):
        if notifier_type == "email":
            return EmailNotifier()
        elif notifier_type == "sms":
            return SMSNotifier()
        elif notifier_type == "push":
            return PushNotifier()
        else:
            raise ValueError(f"Unknown notifier type: {notifier_type}")

In this code, we define three Notifier classes, each with a dispatch method.

Note: In a real application, these will have different implementations to send notifications.

NotificationFactory Class is a static method It is called create_notifier. This is our factory method. It takes a string parameter and returns the appropriate Notifier object.

@staticmethod A decorator means we can call this method without creating an instance of the factory. We only use NotificationFactory.create_notifier().


notifier = NotificationFactory.create_notifier("email")
result = notifier.send("Hello, World!")

Now, whenever we need a notifier, we call the immediate classes directly. This centralizes our object creation logic in one place.

Using a dictionary for cleaner code

The IF-ELIF chain in our factory may become obsolete as we add more informant types. Let’s refactor using a dictionary:

class NotificationFactory:
    notifier_types = {
        "email": EmailNotifier,
        "sms": SMSNotifier,
        "push": PushNotifier
    }

    @staticmethod
    def create_notifier(notifier_type):
        notifier_class = NotificationFactory.notifier_types.get(notifier_type)
        if notifier_class:
            return notifier_class()
        else:
            raise ValueError(f"Unknown notifier type: {notifier_type}")

This approach is much cleaner. We store a dictionary that maps strings to class objects and No For example. The keys are the declarative type names, and the values ​​are the actual class references.

get The method retrieves the class from the dictionary. If the key does not exist, it returns None. We then instantiate the class by calling it with parentheses: notifier_class().


email_notifier = NotificationFactory.create_notifier("email")
sms_notifier = NotificationFactory.create_notifier("sms")
push_notifier = NotificationFactory.create_notifier("push")

This makes it easy to add new reporting types. You just add another entry to the dictionary.

Factory sample with parameters

Real-world objects often require configuration. Let’s extend our factory to handle notifiers that require initialization parameters.

We will create a document generator that produces different file formats with customizable settings.

class PDFDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "PDF"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class WordDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "DOCX"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class MarkdownDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "MD"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class DocumentFactory:
    document_types = {
        "pdf": PDFDocument,
        "word": WordDocument,
        "markdown": MarkdownDocument
    }

    @staticmethod
    def create_document(doc_type, title, author):
        document_class = DocumentFactory.document_types.get(doc_type)
        if document_class:
            return document_class(title, author)
        else:
            raise ValueError(f"Unknown document type: {doc_type}")

The key difference here is that our factory method now accepts additional parameters.

create_document Procedure takes doc_typefor , for , for , . titleand author As arguments. When we speed up the class we pass title And author to create_document Builder: document_class(title, author).


pdf = DocumentFactory.create_document("pdf", "Python Guide", "Tutorial Team")
word = DocumentFactory.create_document("word", "Meeting Notes", "Grace Dev")
markdown = DocumentFactory.create_document("markdown", "README", "DevTeam")

This allows us to create fully configured objects through the factory, keeping the creation logic central.

Using abstract base classes

To make our factory more robust, we can use Python Abstract Base Class (ABC) To implement a common interface.

Let’s create a super simple payment processing system:

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

    @abstractmethod
    def refund(self, transaction_id):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):
        return f"Processing ${amount} via Credit Card"

    def refund(self, transaction_id):
        return f"Refunding credit card transaction {transaction_id}"

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):
        return f"Processing ${amount} via PayPal"

    def refund(self, transaction_id):
        return f"Refunding PayPal transaction {transaction_id}"

class PaymentFactory:
    processors = {
        "credit_card": CreditCardProcessor,
        "paypal": PayPalProcessor
    }

    @staticmethod
    def create_processor(processor_type):
        processor_class = PaymentFactory.processors.get(processor_type)
        if processor_class:
            return processor_class()
        else:
            raise ValueError(f"Unknown processor type: {processor_type}")

here, PaymentProcessor A class defines an interface that all Payment processors must be implemented. @abstractmethod A decorator marks methods that subclasses should override.

You cannot institute PaymentProcessor Directly it only serves as a blueprint. All concrete processors (CreditCardProcessorfor , for , for , . PayPalProcessor) should implement both process_payment And refund Methods If they don’t, Python will raise an error. This guarantees that any object created by our factory will have predictable methods, making our code more predictable and safer.

You can use the factory like this:

processor = PaymentFactory.create_processor("paypal")

Another helpful example: the database connection factory

Let’s create something practical: a factory that creates different database connection objects based on a configuration.

class MySQLConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "MySQL"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on MySQL: {query}"

class PostgreSQLConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "PostgreSQL"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on PostgreSQL: {query}"

class SQLiteConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "SQLite"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on SQLite: {query}"

class DatabaseFactory:
    db_types = {
        "mysql": MySQLConnection,
        "postgresql": PostgreSQLConnection,
        "sqlite": SQLiteConnection
    }

    @staticmethod
    def create_connection(db_type, host, database):
        db_class = DatabaseFactory.db_types.get(db_type)
        if db_class:
            return db_class(host, database)
        else:
            raise ValueError(f"Unknown database type: {db_type}")

    @staticmethod
    def create_from_config(config):
        """Create a database connection from a configuration dictionary"""
        return DatabaseFactory.create_connection(
            config("type"),
            config("host"),
            config("database")
        )

This example shows a more realistic use case. We have multiple database connection classes, each with the same interface but different implementations.

There are two ways to create a factory: create_connection and for direct parameters create_from_config For dictionary creation.

create_from_config method is particularly useful because it allows you to load database settings from a config file or environment variables and create the appropriate connection object.

This pattern makes it easy to switch between different databases without changing your application code. You just change the configuration as shown:


db1 = DatabaseFactory.create_connection("mysql", "localhost", "myapp_db")
print(db1.connect())
print(db1.execute_query("SELECT * FROM users"))


config = {
    "type": "postgresql",
    "host": "db.example.com",
    "database": "production_db"
}
db2 = DatabaseFactory.create_from_config(config)

When using the factory pattern

The factory pattern is useful when you have the following:

  1. Multiple related classes: When you have multiple classes that share a common interface but have different implementations (like the payment processors or database connections we had in the examples).

  2. Runtime decisions: When you need to decide which class to instantiate based on user input, configuration, or other runtime conditions.

  3. Complex object creation: When creating an object involves multiple steps or requires specific logic that you want to encapsulate.

However, do not use the factory pattern when:

  • You only have one or two simple classes

  • Object creation is straightforward with no special logic

  • Additional abstractions make your code harder to understand

wrap up

The factory pattern is a useful tool for managing object creation in Python. It helps you write cleaner, more maintainable code by centralizing the creation logic and decoupling your code from specific class implementations. We have covered:

  • Basic factory implementation with simple examples

  • Using the d dictionary for cleaner factory code

  • Passing parameters to factory generated objects

  • Using abstract base classes for cleaner interfaces

The key path is: Whenever you find yourself repeatedly writing object creation code or needing to decide which class to instantiate at runtime, consider using the factory pattern.. Start simple and add complexity only when needed. A basic dictionary-based factory is often all you need in most applications.

Happy coding!

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro