The factory method in Python is based on a single function that's written to handle our object creation task. ๐๐
We execute it, passing a parameter that provides information about what we want. As a result, the object we wanted is created.
Interestingly, when we use the factory method, we don't need to know any details about how the resulting object is implemented and where it is coming from.
The underlying idea here is that, when developing code, you may instantiate objects directly in methods or in classes. While this is quite normal, you may want to add an extra abstraction between the creation of the object and where it is used in your project.
You can use the Factory pattern to add that extra abstraction. Adding an extra abstraction will also allow you to dynamically choose classes to instantiate based on some kind of logic.
Before the abstraction, your class or method would directly create a concrete class. After adding the factory abstraction, the concrete class is now created outside of the current class/method, and now in a subclass.
Imagine an application for designing houses and the house has a chair already added on the floor by default. By adding the factory pattern, you could give the option to the user to choose different chairs, and how many at runtime. Instead of the chair being hard coded into the project when it started, the user now has the option to choose.
Adding this extra abstraction also means that the complications of instantiating extra objects can now be hidden from the class or method that is using it.
This separation also makes your code easier to read and document.
-----------
๐ The key Terminologies here
- **Concrete Creator**: The client application, class or method that calls the Creator (Factory method).
- **Product Interface**: The interface describing the attributes and methods that the Factory will require in order to create the final product/object.
- **Creator**: The Factory class. Declares the Factory method that will return the object requested from it.
- **Concrete Product**: The object returned from the Factory. The object implements the Product interface.
-----------
๐ Let's see an example WITH and then WITHOUT the **"Factory design pattern in Python"**
๐ Code without Factory Design Pattern
Consider a simple scenario where we have a system that deals with different types of documents. Each document type can be printed, but the way they are printed might differ.
๐ BELOW CODE IS WITHOUT FACTORY METHOD
```python
class PDFDocument:
def print(self):
print("Printing PDF document...")
class WordDocument:
def print(self):
print("Printing Word document...")
class ExcelDocument:
def print(self):
print("Printing Excel document...")
def print_document(document_type):
if document_type == "pdf":
doc = PDFDocument()
elif document_type == "word":
doc = WordDocument()
elif document_type == "excel":
doc = ExcelDocument()
else:
raise ValueError("Unknown document type")
doc.print()
```
๐ The code WITHOUT FACTORY METHOD has a few issues:
๐ If we want to add a new document type, we have to modify the `print_document` function. This violates the Open/Closed Principle, which states that software entities should be open for extension but closed for modification.
๐ The creation logic of documents is mixed with the printing logic in the `print_document` function. This makes the function less cohesive and harder to maintain.
๐ Refactored Code with Factory Design Pattern
To solve the above issues, we can use the Factory design pattern.
๐ Here's how the CODE WITH FACTORY METHOD pattern solves the issues:
๐ We've separated the creation logic from the printing logic. The `DocumentFactory` class is responsible for creating documents, while the `print_document` function is only responsible for printing.
๐ If we want to add a new document type, we only need to modify the `DocumentFactory` class. This makes our code more maintainable and adheres to the Open/Closed Principle.
๐ The `Document` class (Product Interface) ensures that all document types (Concrete Products) have a `print` method. This provides a consistent interface for the client code (Concrete Creator).
๐ The `DocumentFactory` class (Creator) abstracts away the creation logic, allowing the client code to remain unchanged even if the underlying creation logic changes.