Factory Pattern
Define an interface for creating an object, but let subclasses decide which class to instantiate.
Problem
You need to create objects of different types based on some criteria, but you don't want to hardcode new ClassName() in multiple places.
Solution
The Factory pattern abstracts object creation into a factory method or factory class.
Implementation
from abc import ABC, abstractmethod
from typing import List
# ============ PRODUCT INTERFACE ============
class Burger(ABC):
@abstractmethod
def prepare(self) -> str:
pass
@abstractmethod
def get_price(self) -> float:
pass
# ============ CONCRETE PRODUCTS ============
class CheeseBurger(Burger):
def __init__(self):
self.ingredients = ["bun", "cheese", "beef-patty"]
def prepare(self) -> str:
return f"CheeseBurger with {', '.join(self.ingredients)}"
def get_price(self) -> float:
return 5.99
class VeganBurger(Burger):
def __init__(self):
self.ingredients = ["bun", "veggies", "special-sauce"]
def prepare(self) -> str:
return f"VeganBurger with {', '.join(self.ingredients)}"
def get_price(self) -> float:
return 6.99
class SpecialBurger(Burger):
def __init__(self):
self.ingredients = ["bun", "double-patty", "bacon", "cheese", "lettuce"]
def prepare(self) -> str:
return f"SpecialBurger with {', '.join(self.ingredients)}"
def get_price(self) -> float:
return 8.99
# ============ SIMPLE FACTORY ============
class BurgerFactory:
"""Simple Factory - not a design pattern, just a helper class"""
@staticmethod
def create_burger(burger_type: str) -> Burger:
if burger_type == "cheese":
return CheeseBurger()
elif burger_type == "vegan":
return VeganBurger()
elif burger_type == "special":
return SpecialBurger()
else:
raise ValueError(f"Unknown burger type: {burger_type}")
# ============ FACTORY METHOD PATTERN ============
class Restaurant(ABC):
"""Abstract creator"""
@abstractmethod
def create_burger(self) -> Burger:
"""Factory method - subclasses decide which burger to create"""
pass
def order_burger(self) -> str:
burger = self.create_burger()
return burger.prepare()
class FastFoodRestaurant(Restaurant):
"""Concrete creator - creates CheeseBurger"""
def create_burger(self) -> Burger:
return CheeseBurger()
class HealthyRestaurant(Restaurant):
"""Concrete creator - creates VeganBurger"""
def create_burger(self) -> Burger:
return VeganBurger()
class PremiumRestaurant(Restaurant):
"""Concrete creator - creates SpecialBurger"""
def create_burger(self) -> Burger:
return SpecialBurger()
# ============ ABSTRACT FACTORY PATTERN ============
class Pizza(ABC):
@abstractmethod
def prepare(self) -> str:
pass
class MargheritaPizza(Pizza):
def prepare(self) -> str:
return "Margherita Pizza"
class PepperoniPizza(Pizza):
def prepare(self) -> str:
return "Pepperoni Pizza"
class Drink(ABC):
@abstractmethod
def prepare(self) -> str:
pass
class Cola(Drink):
def prepare(self) -> str:
return "Cola"
class OrangeJuice(Drink):
def prepare(self) -> str:
return "Orange Juice"
class MealFactory(ABC):
"""Abstract factory - creates families of related objects"""
@abstractmethod
def create_pizza(self) -> Pizza:
pass
@abstractmethod
def create_drink(self) -> Drink:
pass
class ItalianMealFactory(MealFactory):
"""Concrete factory - creates Italian meals"""
def create_pizza(self) -> Pizza:
return MargheritaPizza()
def create_drink(self) -> Drink:
return OrangeJuice()
class AmericanMealFactory(MealFactory):
"""Concrete factory - creates American meals"""
def create_pizza(self) -> Pizza:
return PepperoniPizza()
def create_drink(self) -> Drink:
return Cola()
# ============ REGISTRY FACTORY ============
class RegistryFactory:
"""Factory using a registry - extensible without modifying factory"""
_registry = {}
@classmethod
def register(cls, burger_type: str, burger_class):
cls._registry[burger_type] = burger_class
@classmethod
def create(cls, burger_type: str) -> Burger:
burger_class = cls._registry.get(burger_type)
if burger_class is None:
raise ValueError(f"Unknown type: {burger_type}")
return burger_class()
# Register types
RegistryFactory.register("cheese", CheeseBurger)
RegistryFactory.register("vegan", VeganBurger)
RegistryFactory.register("special", SpecialBurger)
# Demo
if __name__ == "__main__":
print("=== Simple Factory ===")
factory = BurgerFactory()
burger1 = factory.create_burger("cheese")
burger2 = factory.create_burger("vegan")
print(burger1.prepare())
print(burger2.prepare())
print()
print("=== Factory Method Pattern ===")
restaurants = [
FastFoodRestaurant(),
HealthyRestaurant(),
PremiumRestaurant()
]
for restaurant in restaurants:
print(restaurant.order_burger())
print()
print("=== Abstract Factory Pattern ===")
italian_factory = ItalianMealFactory()
american_factory = AmericanMealFactory()
print(f"Italian meal: {italian_factory.create_pizza().prepare()}, {italian_factory.create_drink().prepare()}")
print(f"American meal: {american_factory.create_pizza().prepare()}, {american_factory.create_drink().prepare()}")
print()
print("=== Registry Factory ===")
burger = RegistryFactory.create("vegan")
print(burger.prepare())
Key Concepts
- Simple Factory: Helper class with static method
- Factory Method: Subclasses override method to decide which object to create
- Abstract Factory: Creates families of related objects
- Registry Factory: Objects registered by type for lookup
When to Use
✅ Complex object creation logic
✅ Need to support multiple types/implementations
✅ Want to decouple creation from usage
✅ Extensibility: add new types without modifying existing code
Interview Q&A
Q1: What's the difference between Simple Factory and Factory Method?
A: - Simple Factory: Static method returns object. Not extensible (modify factory to add type). - Factory Method: Abstract method in subclasses. Extensible (add new subclass).
# Simple Factory (NOT extensible)
class BurgerFactory:
@staticmethod
def create(type):
if type == "new_type": # Have to modify factory
return NewBurger()
# Factory Method (extensible)
class NewRestaurant(Restaurant): # Just add new subclass
def create_burger(self):
return NewBurger()
Q2: When would you use Abstract Factory vs Factory Method?
A: - Factory Method: One family of objects (burgers) - Abstract Factory: Multiple families of related objects (pizza + drink combos)
# Factory Method: Single product
class Restaurant:
def create_burger(self) -> Burger:
pass
# Abstract Factory: Multiple products
class MealFactory:
def create_pizza(self) -> Pizza:
pass
def create_drink(self) -> Drink:
pass
Q3: How do you handle factory creation with many parameters?
A: Use Builder pattern inside factory:
class ComplexProductFactory:
@staticmethod
def create_with_builder(config: Dict) -> ComplexProduct:
return (ComplexProductBuilder()
.set_property1(config.get("prop1"))
.set_property2(config.get("prop2"))
.build())
Q4: How would you implement a registry factory that supports plugins?
A:
class PluginFactory:
_plugins = {}
@classmethod
def register_plugin(cls, name: str, plugin_class):
cls._plugins[name] = plugin_class
@classmethod
def create(cls, name: str, *args, **kwargs):
if name not in cls._plugins:
raise ValueError(f"Plugin not found: {name}")
return cls._plugins[name](*args, **kwargs)
# Plugins can be loaded dynamically
import importlib
plugin_module = importlib.import_module("my_plugin")
PluginFactory.register_plugin("custom", plugin_module.CustomClass)
Q5: How do you test factory pattern code?
A: Use dependency injection and mock:
# Make factory injectable
class Service:
def __init__(self, factory: BurgerFactory):
self.factory = factory
def get_burger(self, type: str) -> Burger:
return self.factory.create(type)
# In tests:
class MockFactory:
def create(self, type: str):
return MagicMock(spec=Burger)
service = Service(MockFactory())
Trade-offs
✅ Pros: Decouples creation from usage, extensible, supports polymorphism
❌ Cons: Extra classes, can be overkill for simple object creation
Real-World Examples
- Object databases (create Document, Image, Video objects)
- UI frameworks (create Button, TextBox, Dialog widgets)
- Data parsers (JSON, XML, YAML parsers)
- Plugin systems (load different implementations dynamically)