Python: Start using Factory Pattern design

create objects without specifying the exact class

Pravash
8 min readMar 6, 2023
Python Factory Pattern

We all know that the Design patterns are a set of best practices that can be used to solve recurring problems in software development. One of the most popular design patterns is the Factory Pattern.

In this article, I will show you how Factory Pattern can simplify object creation and how you can use it to separate creation from use. So lets dive in —

Introducing Factory Method

The Factory Pattern is a creational design pattern that provides a way to create objects without exposing the creation logic to the client. So this allows you to create objects in a superclass, but allows subclasses to alter the type of objects that will be created

In other words, it provides a way to encapsulate object creation logic and decouple it from the rest of the code.

Basic Implementation of Factory Pattern

Let’s look at an example to see how the Factory Pattern can be implemented in Python.

  • Here I have created a simple class hierarchy for different types of animals as shown below:
class Animal:
def speak(self):
pass

class Dog(Animal):
def speak(self):
return "Woof!"

class Cat(Animal):
def speak(self):
return "Meow!"
  • Here, an Animal class have a speak method. And also have two subclasses, Dog and Cat, that override the speak method to return different sounds.
  • Now, let’s say I want to create a program that creates different types of animals based on user input. I can use the Factory Pattern to create objects without knowing the exact type of animal that will be created, which is very useful as in the later stage when you decide to change out those objects by other objects, you don’t have to change the original code.
    Here is the implementation of Factory Pattern:
class AnimalFactory:
def create_animal(self, animal_type):
if animal_type == "dog":
return Dog()

elif animal_type == "cat":
return Cat()

else:
raise ValueError("Invalid animal type")

# Client code
factory = AnimalFactory()
animal1 = factory.create_animal("dog")
animal2 = factory.create_animal("cat")

As you can see, I have an AnimalFactory class that defines a create_animal method. The create_animal method takes an animal_type argument and returns an instance of the corresponding animal class. If an invalid animal_type is provided, a ValueError is raised.

In the client code or you can say in the main() method, I created an instance of the AnimalFactory class and use the create_animal method to create two different types of animals: a Dog and a Cat.

How does the Factory Pattern Work?

In the Factory Pattern, there is a Creator class that defines an abstract method for creating objects.

The Creator class can be an abstract class or an interface.
Subclasses of the Creator class are responsible for implementing the create method to create the specific type of object.

The Factory Pattern allows the client code to create objects without knowing the exact type of object that will be created.

The basic flow of the Factory Pattern is as follows:

  • The client calls the factory method on the Creator interface.
  • The Creator interface then creates a Concrete Product object and returns it to the client.
  • The client then uses the Concrete Product object.

Use Cases

  • When faced with the task of creating objects in complex systems, the factory pattern can be a useful tool:

Object creation can be a complex task, especially in large-scale systems where there may be many different types of objects that need to be created based on different configurations or requirements.
Creating objects manually can be time-consuming, error-prone, and may lead to code duplication. This is where design patterns such as the Abstract Factory Pattern can be useful.

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Suppose you are building a coffee shop application where customers can order different types of coffee. Each coffee has a name, a price, and a set of ingredients. You could represent each type of coffee as a Python class, with a method for calculating the cost of the coffee based on its ingredients.

class Coffee:
def __init__(self, name, price, ingredients):
self.name = name
self.price = price
self.ingredients = ingredients

def cost(self):
return sum([ingredient.cost() for ingredient in self.ingredients])

class Espresso(Coffee):
def __init__(self):
super().__init__('Espresso', 2.0, [CoffeeBean(), Water()])

class Latte(Coffee):
def __init__(self):
super().__init__('Latte', 3.0, [CoffeeBean(), Milk(), Water()])

The Coffee class is an abstract class that defines the basic attributes of a coffee. The Espresso and Latte classes are concrete subclasses that define the specific ingredients and price for each type of coffee.

Now, suppose you want to add a new type of coffee to your application, such as a cappuccino. You would need to create a new subclass of the Coffee class and define the specific ingredients and price for the cappuccino. However, this approach has a limitation: if you have many different types of coffee, the code for creating each type of coffee can become repetitive and difficult to maintain.

This is where the factory pattern comes in. Instead of creating each type of coffee directly, you can define a CoffeeFactory class that has a method for creating each type of coffee:

class CoffeeFactory:
def create_coffee(self, coffee_type):
if coffee_type == 'espresso':
return Espresso()
elif coffee_type == 'latte':
return Latte()
else:
raise ValueError(f"Invalid coffee type: {coffee_type}")

The CoffeeFactory class has a create_coffee method that takes a coffee_type parameter and returns a new instance of the appropriate type of coffee. Now, when you want to create a new coffee in your application, you can simply call the create_coffee method of the CoffeeFactory class:

factory = CoffeeFactory()
espresso = factory.create_coffee('espresso')
latte = factory.create_coffee('latte')
  • Another use case for the factory pattern involves the issue of creating multiple object instances:

Suppose you are building a simple banking application in Python, and you have defined a BankAccount class to represent a bank account. The class has a balance attribute to store the current balance of the account, and methods to deposit and withdraw money from the account:

class BankAccount:
def __init__(self, balance=0):
self.balance = balance

def deposit(self, amount):
self.balance += amount

def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient balance")
self.balance -= amount

Now, suppose you create two instances of the BankAccount class, one for each of two customers:

customer1_account = BankAccount()
customer2_account = BankAccount()

If both customers deposit some money into their accounts, you would expect their account balances to be updated accordingly. For example:

customer1_account.deposit(100)
customer2_account.deposit(50)
print(customer1_account.balance) # prints 100
print(customer2_account.balance) # prints 50

However, what if you accidentally assign one account object to another? For example:

customer1_account = customer2_account
customer1_account.deposit(100)
print(customer2_account.balance) # prints 100, not 50!

This happens because both customer1_account and customer2_account are now pointing to the same object in memory. So when you deposit money into customer1_account, you are actually updating the balance of the shared object, which affects the balance of customer2_account as well. This can lead to unexpected behavior and difficult-to-debug errors in your program.

To avoid this problem, you can use a design pattern like the Singleton Pattern to ensure that only one instance of a class is created and shared among all parts of your program that need to use it.
This can help you to manage object creation and prevent issues related to multiple object instances.

Here is the implementation in Factory Pattern for the above scenario:

class BankAccount:
def __init__(self, balance=0):
self.balance = balance

def deposit(self, amount):
self.balance += amount

def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient balance")
self.balance -= amount


class BankAccountFactory:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.accounts = {}
return cls._instance

def create_account(self, account_number, balance=0):
if account_number not in self.accounts:
account = BankAccount(balance)
self.accounts[account_number] = account
else:
account = self.accounts[account_number]
return account

In this implementation, we use the Factory Pattern to create and manage instances of the BankAccount class. The BankAccountFactory class is designed as a Singleton, so there is only one instance of it in the program. The create_account method takes an account number and balance as arguments, and returns a BankAccount object. If the account number already exists in the accounts dictionary, the method returns the existing BankAccount object; otherwise, it creates a new one and adds it to the dictionary.

Here’s an example of how to use the BankAccountFactory class to create and manage BankAccount objects:

factory = BankAccountFactory()

account1 = factory.create_account('123')
account2 = factory.create_account('456')

account1.deposit(100)
account2.deposit(50)

print(account1.balance) # prints 100
print(account2.balance) # prints 50

account3 = factory.create_account('123') # returns existing account
account3.deposit(50)
print(account1.balance) # prints 150, not 50!

As you can see, the BankAccountFactory ensures that there is only one instance of each BankAccount object, which prevents issues related to multiple object instances.

  • Data pipeline implementation is another scenario where the factory pattern can be useful:

For example, consider a data pipeline that performs data cleaning, transforming and loading into a database.
You could create a factory class that generates the appropriate steps for each type of data.
The factory class would have a method to generate the processing steps based on the type of data provided as input.

Here is the implementation:

class DataProcessor:
def process(self, data):
pass

class CleanDataProcessor(DataProcessor):
def process(self, data):
# code to clean data
return data

class TransformDataProcessor(DataProcessor):
def process(self, data):
# code to transform data
return data

class LoadDataProcessor(DataProcessor):
def process(self, data):
# code to load data
return data

class DataProcessorFactory:
@staticmethod
def create_data_processor(processor_type):
if processor_type == "clean":
return CleanDataProcessor()
elif processor_type == "transform":
return TransformDataProcessor()
elif processor_type == "load":
return LoadDataProcessor()
else:
raise ValueError("Invalid processor type")

data = [1, 2, 3, 4, 5]
processor = DataProcessorFactory.create_data_processor("clean")
data = processor.process(data)

processor = DataProcessorFactory.create_data_processor("transform")
data = processor.process(data)

processor = DataProcessorFactory.create_data_processor("load")
data = processor.process(data)

In this example, the DataProcessor class serves as the base class for different types of data processing steps (Cleaning, Transforming and Loading). The DataProcessorFactory class is the factory class that encapsulates the object creation process.
The create_data_processor method takes in a processor_type argument and returns an instance of either the CleanDataProcessor, TransformDataProcessor or LoadDataProcessor class based on the type provided.

In conclusion, the Factory Pattern is a powerful and flexible design pattern that is widely used in software development.

It provides a way to create objects without specifying the exact class of object that will be created and centralizes the object creation process in a single place.

By using the Factory Method, developers can create objects of different types based on some input, making the code more flexible, maintainable, and less prone to errors.

I believe this article helped you in understanding and getting a basic idea about this design pattern as it is an essential tool for any software developer to have in their toolkit and can be used in a variety of situations where objects need to be created dynamically based on some input.

Connect with me on LinkedIn

--

--

Pravash

I am a passionate Data Engineer and Technology Enthusiast. Here I am using this platform to share my knowledge and experience on tech stacks.