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:
- Tightly coupled code
- Reduced flexibility
- 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:
- Product: An interface or abstract class that defines the object the factory method creates.
- Concrete Product: The specific objects created by the Concrete Creators.
- Creator: An abstract class or interface that declares the factory method.
- 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
- Loose Coupling: The pattern separates the code that creates objects from the code that uses them, reducing dependencies and making the system more flexible.
- Open/Closed Principle : You can introduce new types of products without breaking existing client code, adhering to the Open/Closed Principle.
- Single Responsibility Principle: The object creation logic is centralized in the factory method, adhering to the Single Responsibility Principle.
- 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
- Increased Complexity: For simple scenarios, implementing the Factory Method Pattern might introduce unnecessary complexity.
- Class Proliferation: As the number of product types grows, you may end up with a large number of creator classes, potentially complicating the codebase.
- Overuse: Not every object creation scenario requires a factory method. Overusing the pattern can lead to over-engineering.
- 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: