๐ Factory Method: Dynamic Document Creator
๐ Overview
The Factory Method Pattern defines an interface for creating an object in a base class but allows subclasses to decide which class to instantiate. This pattern is ideal for frameworks where the base code handles high-level logic (like "opening" or "rendering" a document) while delegating the specific object creation to specialized subclasses.
Core Concepts
- Deferred Instantiation: The base class provides a method for creation but leaves the specific class choice to subclasses.
- Parallel Hierarchies: A hierarchy of "Creators" (e.g.,
Application) mirrors a hierarchy of "Products" (e.g.,Document). - Framework Flexibility: Allows a system to work with any new product type by simply adding a new creator subclass.
๐ญ The Engineering Story & Problem
๐ก The Villain (The Problem)
You're building a "Universal Document Editor." Initially, it only supports .txt files.
class Editor:
def open_file(self, path):
# ๐ก Hardcoded to one type
self.doc = TextDocument(path)
self.doc.render()
PDF and DOCX support, you start adding if/elif blocks to the Editor class. Soon, the Editor (which should handle UI and menus) is bloated with logic for every document format in existence. It's rigid, hard to test, and adding a new format requires hacking the core framework.
๐ฆธ The Hero (The Solution)
The Factory Method introduces "Subclass Responsibility."
We define an abstract Application class with an abstract method: create_document().
1. PDFApplication: Overrides create_document() to return a PDFDocument.
2. WordApplication: Overrides create_document() to return a WordDocument.
The base Application class has a method run_editor() that calls self.create_document(). It doesn't know which document it's getting; it just knows it's a Document that has a render() method. The high-level logic is decoupled from the low-level instantiation.
๐ Requirements & Constraints
- (Functional): Support multiple document formats (PDF, Word) with a unified rendering process.
- (Technical): The base application class must be decoupled from concrete document classes.
- (Technical): New formats must be addable by creating new subclasses, not by modifying existing ones.
๐๏ธ Structure & Blueprint
Class Diagram
classDiagram
direction TB
class Document {
<<interface>>
+render()
}
class PDFDocument {
+render()
}
class WordDocument {
+render()
}
class Application {
<<abstract>>
+create_document() Document*
+open()
}
class PDFApplication {
+create_document() Document
}
class WordApplication {
+create_document() Document
}
Document <|.. PDFDocument
Document <|.. WordDocument
Application <|-- PDFApplication
Application <|-- WordApplication
PDFApplication ..> PDFDocument : creates
WordApplication ..> WordDocument : creates
Runtime Context (Sequence)
sequenceDiagram
participant Client
participant App as PDFApplication
participant Doc as PDFDocument
Client->>App: open()
App->>App: create_document()
App-->>Doc: new PDFDocument()
App->>Doc: render()
Doc-->>Client: Shows PDF content
๐ป Implementation & Code
๐ง SOLID Principles Applied
- Dependency Inversion: The base
Applicationdepends on theDocumentabstraction, not concrete classes likePDFDocument. - Open/Closed: You can add a
MarkdownApplicationwithout changing any existing code.
๐ The Code
The Villain's Code (Without Pattern)
The Hero's Code (With Pattern)
from abc import ABC, abstractmethod
class Document(ABC):
@abstractmethod
def open(self) -> str:
pass
class PDFDocument(Document):
def open(self) -> str:
return "Opening the PDF..."
class WordDocument(Document):
def open(self) -> str:
return "Opening the Word"
class Application(ABC):
@abstractmethod
def factory_method(self) -> Document:
pass
def createDocument(self) -> str:
product = self.factory_method()
return product.open()
class PDFApplication(Application):
def factory_method(self) -> Document:
return PDFDocument()
class WordApplication(Application):
def factory_method(self) -> Document:
return WordDocument()
pdf = PDFApplication()
print(pdf.createDocument())
โ๏ธ Trade-offs & Testing
| Pros (Why it works) | Cons (The Twist / Pitfalls) |
|---|---|
| Decoupling: Framework doesn't know product classes. | Class Explosion: Need a new Application subclass for every Document type. |
| Consistency: Standardized interface for all products. | Complexity: Parallel hierarchies can be hard to track. |
| Flexibility: Subclasses can specialize the creation. | Over-abstraction: If creation is simple, a static factory is better. |
๐งช Testing Strategy
- Unit Test Creators: Verify
PDFApplication.create_document()returns aPDFDocumentinstance. - Test Base Logic: Use a
MockApplicationthat returns aMockDocumentto test the baseopen()logic independently of real file formats.
๐ค Interview Toolkit
- Interview Signal: mastery of framework design and delegation-based creation.
- When to Use:
- "Designing a library where users need to create their own custom types..."
- "A class can't anticipate the specific class of objects it needs to create..."
- "You want to centralize creation logic that requires complex setup..."
- Scalability Probe: "How to avoid creating 50 subclasses?" (Answer: Use a Parameterized Factory Method where the base class takes a 'type' argument, or use a registry of constructors.)
- Design Alternatives:
- Abstract Factory: For creating sets of related objects.
- Prototype: If creation is expensive and you should clone existing objects instead.
๐ Related Patterns
- Abstract Factory โ Abstract Factories often use Factory Methods to create their products.
- Template Method โ Factory Methods are often called within a Template Method.
- Prototype โ Reduces subclassing by cloning objects.