Factory Method Pattern

The Factory Method Pattern is a creational design pattern that provides a way to create objects without specifying their exact class. It’s particularly useful when you need flexibility in creating objects or when you want to decouple object creation from the code that uses the object. Let’s dive into this pattern using a real-world example.

The Problem

In object-oriented programming, we often face situations where we need to create objects of different types based on certain conditions or parameters. As the number of object types grows, direct object instantiation can lead to:

  1. Tightly coupled code
  2. Reduced flexibility
  3. Difficulty in maintaining and extending the codebase

The Solution: Factory Method Pattern

The Factory Method Pattern addresses these issues by introducing a level of abstraction in object creation. Instead of directly instantiating objects, it suggests using a special factory method responsible for creating and returning the appropriate object based on given parameters or conditions.

Key Components

The Factory Method Pattern consists of four main components:

  1. Product: An interface or abstract class that defines the object the factory method creates.
  2. Concrete Product: The specific objects created by the Concrete Creators.
  3. Creator: An abstract class or interface that declares the factory method.
  4. Concrete Creator: A class that implements the Creator and overrides the factory method to return a specific type of object.

Example: Document Creation

Let’s implement a document creation system using the Factory Method Pattern. This system will support creating different types of documents such as Word, PDF, and Excel.

Step 1: Define the Product Interface

First, we define an interface for the products that the factory method will create:

from abc import ABC, abstractmethod

class Document(ABC):
    @abstractmethod
    def open(self) -> str:
        pass

    @abstractmethod
    def save(self) -> str:
        pass

Step 2: Implement Concrete Products

Next, we implement concrete products that adhere to the Document interface:

class WordDocument(Document):
    def open(self) -> str:
        return "Opening Word document"

    def save(self) -> str:
        return "Saving Word document"

class PDFDocument(Document):
    def open(self) -> str:
        return "Opening PDF document"

    def save(self) -> str:
        return "Saving PDF document"

class ExcelDocument(Document):
    def open(self) -> str:
        return "Opening Excel document"

    def save(self) -> str:
        return "Saving Excel document"

Step 3: Define the Creator Interface

We then define an interface for the creator class that declares the factory method:

from abc import ABC, abstractmethod

class DocumentCreator(ABC):
    @abstractmethod
    def create_document(self) -> Document:
        pass

Step 4: Implement Concrete Creators

Now, we implement concrete creators that override the factory method to create specific types of documents:

class WordDocumentCreator(DocumentCreator):
    def create_document(self) -> Document:
        return WordDocument()

class PDFDocumentCreator(DocumentCreator):
    def create_document(self) -> Document:
        return PDFDocument()

class ExcelDocumentCreator(DocumentCreator):
    def create_document(self) -> Document:
        return ExcelDocument()

Step 5: Using the Factory Method

Finally, we can use the factory method to create and use different types of documents without specifying their concrete classes:

def main():
    creators = [WordDocumentCreator(), PDFDocumentCreator(), ExcelDocumentCreator()]

    for creator in creators:
        document = creator.create_document()
        print(document.open())
        print(document.save())

if __name__ == "__main__":
    main()

Advantages of the Factory Method Pattern

  1. Loose Coupling: The pattern separates the code that creates objects from the code that uses them, reducing dependencies and making the system more flexible.
  2. Open/Closed Principle : You can introduce new types of products without breaking existing client code, adhering to the Open/Closed Principle.
  3. Single Responsibility Principle: The object creation logic is centralized in the factory method, adhering to the Single Responsibility Principle.
  4. Flexibility in Object Creation: The pattern allows for runtime decisions on which objects to create based on conditions or parameters. Easier Testing: By using interfaces and dependency injection, the Factory Method Pattern makes it easier to mock objects for testing.

Drawbacks

  1. Increased Complexity: For simple scenarios, implementing the Factory Method Pattern might introduce unnecessary complexity.
  2. Class Proliferation: As the number of product types grows, you may end up with a large number of creator classes, potentially complicating the codebase.
  3. Overuse: Not every object creation scenario requires a factory method. Overusing the pattern can lead to over-engineering.
  4. Performance Overhead: In some cases, using factory methods might introduce a slight performance overhead compared to direct object instantiation.

Conclusion

The Factory Method Pattern is a powerful tool in a developer’s arsenal for managing object creation in complex systems. By providing a flexible and extensible way to create objects, it promotes loose coupling and adherence to SOLID principles. However, like any design pattern, it should be applied judiciously, considering the specific needs and constraints of your project.

When used appropriately, the Factory Method Pattern can significantly improve the maintainability, flexibility, and testability of your code, especially in scenarios involving complex object creation or frequent changes to the types of objects being created.




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • CPU Cache
  • Understanding Linear Blended Skinning in 3D Animation
  • Starvation in Operating Systems
  • Virtual Memory
  • What is Bytecode in Python?
  • Understanding Top P in Language Models
  • LDAP (Lightweight Directory Access Protocol)
  • Kubernetes 13 - Namespaces and Context
  • Kubernetes 12 - Higher Deployment Abstractions in Kubernetes
  • Kubernetes 11 - CRD's and THe Operator Pattern