Definition

“Factory Method pattern defines an interface/method (a.k.a. factory method) for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses”
factory-method

Explanation

You declare a factory method in a base creator class (or interface) (e.g., createDocument() in Application). Concrete subclasses override it to return specific products (e.g., SpreadsheetDocument, TextDocument). Clients call the factory method on the base class interface, but the actual subclass controls the product’s type. This decouples product creation from client code.

Code

In this example, Creator defines the factory method factory_method(), and its subclasses (ConcreteCreator1, ConcreteCreator2) override this method to create specific products (ConcreteProduct1, ConcreteProduct2). The client code uses a creator without knowing the exact product class.
from abc import ABC, abstractmethod

class Creator(ABC):
    @abstractmethod
    def factory_method(self):
        pass
    def some_operation(self) -> str:
        product = self.factory_method()
        return f"Creator: The same creator's code has just worked with {product.operation()}"

class ConcreteCreator1(Creator):
    def factory_method(self):
        return ConcreteProduct1()
class ConcreteCreator2(Creator):
    def factory_method(self):
        return ConcreteProduct2()

class Product(ABC):
    @abstractmethod
    def operation(self) -> str:
        pass
class ConcreteProduct1(Product):
    def operation(self) -> str:
        return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(Product):
    def operation(self) -> str:
        return "{Result of the ConcreteProduct2}"

def client_code(creator: Creator) -> None:
    print(f"Client: Creator is {type(creator).__name__}. {creator.some_operation()}")

def main():
    print("App: Launched with the ConcreteCreator1.")
    client_code(ConcreteCreator1())
    print("\nApp: Launched with the ConcreteCreator2.")
    client_code(ConcreteCreator2())
    print()

if __name__ == "__main__":
    main()

Analogy

A pizza store example: the base class PizzaStore defines orderPizza(type) which calls the abstract createPizza(type). Subclasses like NYPizzaStore and ChicagoPizzaStore implement createPizza to return region-specific pizzas. The orderPizza steps remain the same, while creation varies by subclass.

Interview Insights

Common uses: When a class can’t anticipate the class of objects it must create. Common in frameworks: you extend a base class and provide the object type. Also useful when you want to provide hooks for subclasses to supply products ( e.g. GUI toolkit creating platform-specific widgets).
Advantages: Adheres to Open/Closed: new product types can be added by subclassing without modifying base code. Encapsulates object creation code.
Disadvantages: Requires a new subclass for each product variant, leading to many classes. Adds complexity (indirection) for simple cases.\

Alternate Explanation

Problem Statement

When you have a class that needs to create objects, you often use new (in Python, just call the class). But hardcoding which class to instantiate makes the code rigid and harder to extend or change. Imagine you’re ordering food delivery: You don’t care which person prepares your food. You just ask for a “Pizza” or a “Burger”, and the restaurant has a method to decide how and who prepares it. This “method” is the Factory Method — it decides which concrete class (Chef) will make your product (food).
Notification Example
from abc import ABC, abstractmethod

# Step 1: Product Interface
class Notification(ABC):
    @abstractmethod
    def notify(self, message: str):
        pass

# Step 2: Concrete Products
class EmailNotification(Notification):
    def notify(self, message: str):
        print(f"Email: {message}")

class SMSNotification(Notification):
    def notify(self, message: str):
        print(f"SMS: {message}")

# Step 3: Creator (Abstract)
class NotificationSender(ABC):
    @abstractmethod
    def create_notification(self) -> Notification:
        """Factory method"""
        pass

    def send(self, message: str):
        # Uses the factory method
        notifier = self.create_notification()
        notifier.notify(message)

# Step 4: Concrete Creators
class EmailSender(NotificationSender):
    def create_notification(self) -> Notification:
        return EmailNotification()

class SMSSender(NotificationSender):
    def create_notification(self) -> Notification:
        return SMSNotification()

# Step 5: Client code
def client_code(sender: NotificationSender, message: str):
    sender.send(message)

# Usage
client_code(EmailSender(), "You've got mail!")
client_code(SMSSender(), "This is a text message.")