Understanding Factory Method in Python

Manishankar Jaiswal
5 min readSep 13, 2024

--

In this blog post, we will explore the Factory Method Pattern using Python, which is a popular design pattern for creating objects. The Factory Method Pattern allows us to create objects without exposing the creation logic to the client and refers to the newly created object through a common interface.

Factory Method Design Pattern
Factory Method

We will walk through an example of how to use the Factory Method Pattern dynamically by building a system for different payment methods. Even if you’re a newcomer, this post will break down each concept step by step to ensure clarity.

What is the Factory Method Pattern?

The Factory Method Pattern is part of the creational design patterns category. The main purpose of this pattern is to delegate the instantiation of objects to subclasses. Instead of calling a constructor directly, the client will use a factory method to create objects. This allows us to centralize object creation and add flexibility in the process.

Key Features of the Factory Method Pattern:

  • Encapsulation: The client does not need to know how the objects are created; it only interacts with an interface or abstract class.
  • Flexibility: You can add new types of objects without modifying the client code, making the system extendable.
  • Loose Coupling: The client is decoupled from the object creation logic, enhancing maintainability and scalability.

Code Walkthrough

We’ll now dive into a Python-based implementation of the Factory Method Pattern, focusing on dynamically creating various payment methods like PayPal, Credit Card, Bitcoin, and Google Pay.

1. Abstract Payment Class

The Product in the Factory Method Pattern represents an abstract class or interface. In our case, it is the Payment class that defines the common interface for all payment methods.

# payment_methods.py

from abc import ABC, abstractmethod

# Product (Interface)
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass

Explanation:

  • Payment is an abstract base class (ABC) with the abstract method pay(). Each concrete payment method (like PayPal, Credit Card) will implement this method, ensuring uniformity across all payment types.

2. Concrete Payment Classes

These are the actual implementations of the Payment class. Each class implements the pay() method and defines how the specific payment type processes a transaction.

# Concrete Products (Payment Methods)
class CreditCardPayment(Payment):
def pay(self, amount):
print(f"Paying {amount} using Credit Card.")

class PayPalPayment(Payment):
def pay(self, amount):
print(f"Paying {amount} using PayPal.")

class BitcoinPayment(Payment):
def pay(self, amount):
print(f"Paying {amount} using Bitcoin.")

class GooglePayment(Payment):
def pay(self, amount):
print(f"Paying {amount} using Google.")

Explanation:

  • Each class inherits from the Payment abstract class and provides its implementation of the pay() method. This allows you to easily add new payment methods without changing the existing code.

3. Dynamic Payment Factory

The Factory is responsible for creating instances of payment methods. The DynamicPaymentFactory uses reflection to dynamically discover available payment methods and creates them on demand.

# dynamic_payment_factory.py

from inspect import getmembers, isclass, isabstract
import payment_methods

# Creator
class DynamicPaymentFactory:
payment_dictionary = {}

def __init__(self):
self.load_payment_methods()

def load_payment_methods(self):
# Get all non-abstract classes from the payment_methods module
members = getmembers(payment_methods, lambda m: isclass(m) and not isabstract(m))
for name, _type in members:
if isclass(_type) and issubclass(_type, payment_methods.Payment):
self.payment_dictionary[name] = _type

def create(self, payment_type: str):
# Check if the requested payment method is available
if payment_type in self.payment_dictionary:
return self.payment_dictionary[payment_type]()
else:
raise ValueError(f"{payment_type} is not currently supported as a payment method.")

Explanation:

  • DynamicPaymentFactory is our Factory that holds a dictionary (payment_dictionary) mapping payment method names to their corresponding classes.
  • The load_payment_methods() method uses the inspect module to dynamically discover all non-abstract classes in the payment_methods module and loads them into the dictionary. This allows the factory to be easily extendable by simply adding new payment methods in the future.
  • The create() method checks if the requested payment method exists in the dictionary and, if so, returns an instance of the class. If not, it raises an error, indicating that the payment method is not supported.

4. Client Code

Finally, let’s look at how the client code interacts with the DynamicPaymentFactory to dynamically choose and use payment methods.

# client_code.py

from dynamic_payment_factory import DynamicPaymentFactory

# Initialize the dynamic factory
factory = DynamicPaymentFactory()

# Choose a payment method dynamically
payment_method = factory.create('PayPalPayment')
payment_method.pay(100) # Outputs: "Paying 100 using PayPal."

payment_method = factory.create('BitcoinPayment')
payment_method.pay(200) # Outputs: "Paying 200 using Bitcoin."

payment_method = factory.create('CreditCardPayment')
payment_method.pay(300) # Outputs: "Paying 300 using Credit Card."

payment_method = factory.create('GooglePayment')
payment_method.pay(400) # Outputs: "Paying 400 using Google."

Explanation:

  • The client initializes the DynamicPaymentFactory, allowing it to dynamically load all available payment methods.
  • The client can then request a payment method by name (e.g., 'PayPalPayment', 'BitcoinPayment'), and the factory returns an instance of the appropriate class.
  • The client can then use the pay() method to make the payment.

Architecture Overview

The architecture of this Factory Method implementation consists of three main components:

  • Product (Abstract Class): Payment is the abstract base class that enforces the interface for all payment methods.
  • Concrete Products: These are the specific payment methods like CreditCardPayment, PayPalPayment, etc., that implement the pay() method.
  • Creator (Factory): DynamicPaymentFactory is responsible for dynamically creating instances of the correct payment method based on user input.

Benefits of Using Factory Method:

  • Scalability: You can easily add new payment methods (e.g., ApplePay, Stripe) by simply creating a new class that inherits from Payment without modifying existing code.
  • Separation of Concerns: The client doesn’t need to know about the creation process. It just needs to call the factory and use the returned object.
  • Maintainability: All logic related to object creation is centralized in the factory, making it easier to manage and extend.

Conclusion

The Factory Method Pattern provides a robust way to dynamically create objects while keeping the client code simple and clean. By using this pattern, you can improve scalability, maintainability, and flexibility in your applications. The example of dynamic payment methods in this post demonstrates how powerful and useful this pattern can be, especially in scenarios where new types of objects are frequently introduced.

I hope this post gave you a clear understanding of how to implement the Factory Method in Python. Keep experimenting with different scenarios, and soon you’ll be able to design your own flexible and scalable systems using design patterns!

Happy Coding!

--

--

No responses yet