Skip to content

Abstract Factory Pattern

Create families of related objects - ensure compatibility across product families!

Abstract Factory Pattern: Creating Families of Objects

Section titled “Abstract Factory Pattern: Creating Families of Objects”

Now let’s dive into the Abstract Factory Pattern - a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Imagine you’re ordering a complete meal from a restaurant. You want everything to match - Italian restaurant gives you Italian appetizer, Italian main course, and Italian dessert. The Abstract Factory Pattern works the same way!

The Abstract Factory Pattern lets you create families of related objects. Instead of creating individual objects, you create a factory that produces a complete set of compatible objects.

What’s the Use of Abstract Factory Pattern?

Section titled “What’s the Use of Abstract Factory Pattern?”

The Abstract Factory Pattern is useful when:

  1. You need families of related objects - Objects that must work together
  2. You want to ensure compatibility - Objects from same family are compatible
  3. You need to switch families - Easy to switch between different families
  4. You want to hide implementation - Client doesn’t know concrete classes
  5. You need consistency - All objects in a family follow same style/theme

What Happens If We Don’t Use Abstract Factory Pattern?

Section titled “What Happens If We Don’t Use Abstract Factory Pattern?”

Without the Abstract Factory Pattern, you might:

  • Create incompatible objects - Mix objects from different families
  • Scatter creation logic - Object creation logic spread everywhere
  • Violate consistency - Objects don’t match or work together
  • Make it hard to switch families - Need to modify code everywhere
  • Tight coupling - Client code depends on concrete classes

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Abstract Factory Pattern works in practice - showing how families of objects are created:

sequenceDiagram
    participant Client
    participant Factory as AbstractFactory
    participant ItalianFactory
    participant Products as Italian Products
    
    Client->>Factory: getFactory("italian")
    activate Factory
    Factory->>ItalianFactory: create ItalianFactory
    activate ItalianFactory
    ItalianFactory-->>Factory: factory instance
    deactivate ItalianFactory
    Factory-->>Client: ItalianFactory
    deactivate Factory
    
    Client->>ItalianFactory: create_appetizer()
    activate ItalianFactory
    ItalianFactory->>Products: new ItalianAppetizer()
    Products-->>ItalianFactory: Bruschetta
    ItalianFactory-->>Client: ItalianAppetizer
    deactivate ItalianFactory
    
    Client->>ItalianFactory: create_main()
    activate ItalianFactory
    ItalianFactory->>Products: new ItalianMain()
    Products-->>ItalianFactory: Margherita Pizza
    ItalianFactory-->>Client: ItalianMain
    deactivate ItalianFactory
    
    Client->>ItalianFactory: create_dessert()
    activate ItalianFactory
    ItalianFactory->>Products: new ItalianDessert()
    Products-->>ItalianFactory: Tiramisu
    ItalianFactory-->>Client: ItalianDessert
    deactivate ItalianFactory
    
    Note over Client,Products: All products from<br/>same Italian family!

You’re building a pizza restaurant system that serves complete meals. Each meal has an appetizer, main course, and dessert. You want to ensure all items in a meal match (Italian meal = Italian appetizer + Italian main + Italian dessert). Without Abstract Factory Pattern:

bad_meal.py
# ❌ Without Abstract Factory Pattern - Can create incompatible objects!
class ItalianAppetizer:
def serve(self):
return "🍞 Serving Bruschetta"
class ItalianMain:
def serve(self):
return "🍕 Serving Margherita Pizza"
class ItalianDessert:
def serve(self):
return "🍰 Serving Tiramisu"
class AmericanAppetizer:
def serve(self):
return "🍞 Serving Garlic Bread"
class AmericanMain:
def serve(self):
return "🍕 Serving Pepperoni Pizza"
class AmericanDessert:
def serve(self):
return "🍰 Serving Cheesecake"
# Problem: Can accidentally mix incompatible objects!
def create_meal(style: str):
if style == "italian":
appetizer = ItalianAppetizer()
main = ItalianMain()
dessert = ItalianDessert()
elif style == "american":
appetizer = AmericanAppetizer()
main = AmericanMain()
dessert = AmericanDessert()
else:
raise ValueError("Unknown style")
return [appetizer, main, dessert]
# Problem: Easy to make mistakes!
meal1 = create_meal("italian")
meal2 = [ItalianAppetizer(), AmericanMain(), ItalianDessert()] # Mixed! ❌
# Problems:
# - Can mix objects from different families
# - Creation logic scattered
# - Hard to ensure consistency
# - Need to modify code to add new families

Problems:

  • Can mix incompatible objects from different families
  • Creation logic scattered
  • Hard to ensure consistency
  • Need to modify code to add new families
classDiagram
    class PizzaMealFactory {
        <<abstract>>
        +create_appetizer() Appetizer
        +create_main() MainCourse
        +create_dessert() Dessert
    }
    class ItalianFactory {
        +create_appetizer() ItalianAppetizer
        +create_main() ItalianMain
        +create_dessert() ItalianDessert
    }
    class AmericanFactory {
        +create_appetizer() AmericanAppetizer
        +create_main() AmericanMain
        +create_dessert() AmericanDessert
    }
    class Appetizer {
        <<abstract>>
        +serve() str
    }
    class MainCourse {
        <<abstract>>
        +serve() str
    }
    class Dessert {
        <<abstract>>
        +serve() str
    }
    class ItalianAppetizer {
        +serve() str
    }
    class ItalianMain {
        +serve() str
    }
    class ItalianDessert {
        +serve() str
    }
    class AmericanAppetizer {
        +serve() str
    }
    class AmericanMain {
        +serve() str
    }
    class AmericanDessert {
        +serve() str
    }
    
    PizzaMealFactory <|-- ItalianFactory : implements
    PizzaMealFactory <|-- AmericanFactory : implements
    PizzaMealFactory ..> Appetizer : creates
    PizzaMealFactory ..> MainCourse : creates
    PizzaMealFactory ..> Dessert : creates
    Appetizer <|-- ItalianAppetizer : implements
    Appetizer <|-- AmericanAppetizer : implements
    MainCourse <|-- ItalianMain : implements
    MainCourse <|-- AmericanMain : implements
    Dessert <|-- ItalianDessert : implements
    Dessert <|-- AmericanDessert : implements
    
    note for PizzaMealFactory "Creates families of\ncompatible objects"
    note for ItalianFactory "All products are Italian"
    note for AmericanFactory "All products are American"
pizza_meal_factory.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define abstract product interfaces
class Appetizer(ABC):
"""Abstract product: Appetizer"""
@abstractmethod
def serve(self) -> str:
pass
class MainCourse(ABC):
"""Abstract product: Main Course"""
@abstractmethod
def serve(self) -> str:
pass
class Dessert(ABC):
"""Abstract product: Dessert"""
@abstractmethod
def serve(self) -> str:
pass
# Step 2: Define abstract factory
class PizzaMealFactory(ABC):
"""Abstract Factory - creates families of related products"""
@abstractmethod
def create_appetizer(self) -> Appetizer:
"""Create an appetizer"""
pass
@abstractmethod
def create_main(self) -> MainCourse:
"""Create a main course"""
pass
@abstractmethod
def create_dessert(self) -> Dessert:
"""Create a dessert"""
pass
# Step 3: Create concrete products for Italian family
class ItalianAppetizer(Appetizer):
def serve(self) -> str:
return "🍞 Serving Bruschetta"
class ItalianMain(MainCourse):
def serve(self) -> str:
return "🍕 Serving Margherita Pizza"
class ItalianDessert(Dessert):
def serve(self) -> str:
return "🍰 Serving Tiramisu"
# Step 4: Create concrete products for American family
class AmericanAppetizer(Appetizer):
def serve(self) -> str:
return "🍞 Serving Garlic Bread"
class AmericanMain(MainCourse):
def serve(self) -> str:
return "🍕 Serving Pepperoni Pizza"
class AmericanDessert(Dessert):
def serve(self) -> str:
return "🍰 Serving Cheesecake"
# Step 5: Create concrete factories
class ItalianPizzaMealFactory(PizzaMealFactory):
"""Concrete Factory - creates Italian meal family"""
def create_appetizer(self) -> Appetizer:
return ItalianAppetizer()
def create_main(self) -> MainCourse:
return ItalianMain()
def create_dessert(self) -> Dessert:
return ItalianDessert()
class AmericanPizzaMealFactory(PizzaMealFactory):
"""Concrete Factory - creates American meal family"""
def create_appetizer(self) -> Appetizer:
return AmericanAppetizer()
def create_main(self) -> MainCourse:
return AmericanMain()
def create_dessert(self) -> Dessert:
return AmericanDessert()
# Step 6: Factory provider (optional)
class MealFactoryProvider:
"""Provides the appropriate factory"""
@staticmethod
def get_factory(style: str) -> PizzaMealFactory:
if style.lower() == "italian":
return ItalianPizzaMealFactory()
elif style.lower() == "american":
return AmericanPizzaMealFactory()
else:
raise ValueError(f"Unknown meal style: {style}")
# Step 7: Use the pattern
def create_complete_meal(factory: PizzaMealFactory) -> List[str]:
"""Create a complete meal using the factory"""
appetizer = factory.create_appetizer()
main = factory.create_main()
dessert = factory.create_dessert()
return [
appetizer.serve(),
main.serve(),
dessert.serve()
]
# Usage
def main():
# Create Italian meal - all items are Italian!
italian_factory = MealFactoryProvider.get_factory("italian")
italian_meal = create_complete_meal(italian_factory)
print("🇮🇹 Italian Meal:")
for item in italian_meal:
print(f" {item}")
print()
# Create American meal - all items are American!
american_factory = MealFactoryProvider.get_factory("american")
american_meal = create_complete_meal(american_factory)
print("🇺🇸 American Meal:")
for item in american_meal:
print(f" {item}")
# Can't mix families - factory ensures consistency!
# italian_factory.create_appetizer() + american_factory.create_main() ❌
if __name__ == "__main__":
main()

Real-World Software Example: UI Component Factory

Section titled “Real-World Software Example: UI Component Factory”

Now let’s see a realistic software example - a UI framework that needs to create families of UI components (buttons, dialogs, menus) that match the operating system theme.

You’re building a cross-platform UI framework. Each OS (Windows, macOS, Linux) has different UI components. You need to ensure all components in an application match the OS theme. Without Abstract Factory Pattern:

bad_ui.py
# ❌ Without Abstract Factory Pattern - Can create incompatible UI components!
class WindowsButton:
def render(self):
return "🪟 Windows Button"
class WindowsDialog:
def render(self):
return "🪟 Windows Dialog"
class MacButton:
def render(self):
return "🍎 Mac Button"
class MacDialog:
def render(self):
return "🍎 Mac Dialog"
# Problem: Can accidentally mix incompatible components!
def create_ui(os: str):
if os == "windows":
button = WindowsButton()
dialog = WindowsDialog()
elif os == "mac":
button = MacButton()
dialog = MacDialog()
else:
raise ValueError("Unknown OS")
return [button, dialog]
# Problem: Easy to make mistakes!
ui1 = create_ui("windows")
ui2 = [WindowsButton(), MacDialog()] # Mixed! ❌
# Problems:
# - Can mix components from different OS
# - Creation logic scattered
# - Hard to ensure consistency
# - Need to modify code to add new OS

Problems:

  • Can mix incompatible UI components from different OS
  • Creation logic scattered
  • Hard to ensure consistency
  • Need to modify code to add new OS
ui_factory.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define abstract product interfaces
class Button(ABC):
"""Abstract product: Button"""
@abstractmethod
def render(self) -> str:
pass
class Dialog(ABC):
"""Abstract product: Dialog"""
@abstractmethod
def render(self) -> str:
pass
class Menu(ABC):
"""Abstract product: Menu"""
@abstractmethod
def render(self) -> str:
pass
# Step 2: Define abstract factory
class UIFactory(ABC):
"""Abstract Factory - creates families of UI components"""
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_dialog(self) -> Dialog:
pass
@abstractmethod
def create_menu(self) -> Menu:
pass
# Step 3: Create concrete products for Windows family
class WindowsButton(Button):
def render(self) -> str:
return "🪟 Windows Button"
class WindowsDialog(Dialog):
def render(self) -> str:
return "🪟 Windows Dialog"
class WindowsMenu(Menu):
def render(self) -> str:
return "🪟 Windows Menu"
# Step 4: Create concrete products for Mac family
class MacButton(Button):
def render(self) -> str:
return "🍎 Mac Button"
class MacDialog(Dialog):
def render(self) -> str:
return "🍎 Mac Dialog"
class MacMenu(Menu):
def render(self) -> str:
return "🍎 Mac Menu"
# Step 5: Create concrete factories
class WindowsUIFactory(UIFactory):
"""Concrete Factory - creates Windows UI component family"""
def create_button(self) -> Button:
return WindowsButton()
def create_dialog(self) -> Dialog:
return WindowsDialog()
def create_menu(self) -> Menu:
return WindowsMenu()
class MacUIFactory(UIFactory):
"""Concrete Factory - creates Mac UI component family"""
def create_button(self) -> Button:
return MacButton()
def create_dialog(self) -> Dialog:
return MacDialog()
def create_menu(self) -> Menu:
return MacMenu()
# Step 6: Factory provider
class UIFactoryProvider:
"""Provides the appropriate UI factory based on OS"""
@staticmethod
def get_factory(os: str) -> UIFactory:
if os.lower() == "windows":
return WindowsUIFactory()
elif os.lower() == "mac":
return MacUIFactory()
else:
raise ValueError(f"Unknown OS: {os}")
# Step 7: Use the pattern
def create_application_ui(factory: UIFactory) -> List[str]:
"""Create application UI using the factory"""
button = factory.create_button()
dialog = factory.create_dialog()
menu = factory.create_menu()
return [
button.render(),
dialog.render(),
menu.render()
]
# Usage
def main():
# Detect OS (in real app, this would be actual OS detection)
current_os = "windows" # or "mac"
# Create UI factory for current OS
ui_factory = UIFactoryProvider.get_factory(current_os)
# Create application UI - all components match the OS!
ui_components = create_application_ui(ui_factory)
print(f"🖥️ Application UI ({current_os.upper()}):")
for component in ui_components:
print(f" {component}")
# All components are from the same OS family - guaranteed consistency!
if __name__ == "__main__":
main()

There are several ways to implement the Abstract Factory Pattern:

Basic implementation with abstract factory interface:

simple_abstract_factory.py
from abc import ABC, abstractmethod
class AbstractFactory(ABC):
@abstractmethod
def create_product_a(self):
pass
@abstractmethod
def create_product_b(self):
pass
class ConcreteFactory1(AbstractFactory):
def create_product_a(self):
return ProductA1()
def create_product_b(self):
return ProductB1()

Add a provider to get the right factory:

factory_provider.py
class FactoryProvider:
@staticmethod
def get_factory(type: str) -> AbstractFactory:
if type == "type1":
return ConcreteFactory1()
elif type == "type2":
return ConcreteFactory2()
else:
raise ValueError("Unknown type")

Use Abstract Factory Pattern when:

You need families of related objects - Objects that must work together
You want to ensure compatibility - Objects from same family are compatible
You need to switch families - Easy to switch between different families
You want to hide implementation - Client doesn’t know concrete classes
You need consistency - All objects in a family follow same style/theme

Don’t use Abstract Factory Pattern when:

Objects are independent - If objects don’t need to be compatible
Only one product type - If you only need one type of product
Simple object creation - If creation is straightforward
Over-engineering - Don’t add complexity for simple cases


Mistake 1: Mixing Products from Different Families

Section titled “Mistake 1: Mixing Products from Different Families”
mixed_families.py
# ❌ Bad: Mixing products from different families
factory1 = ItalianFactory()
factory2 = AmericanFactory()
meal = [
factory1.create_appetizer(), # Italian
factory2.create_main() # American - incompatible!
]
# ✅ Good: Use same factory for all products
factory = ItalianFactory()
meal = [
factory.create_appetizer(), # Italian
factory.create_main() # Italian - compatible!
]

Mistake 2: Not Using Abstract Factory When Needed

Section titled “Mistake 2: Not Using Abstract Factory When Needed”
not_using_abstract_factory.py
# ❌ Bad: Creating objects individually - can mix incompatible ones
button = WindowsButton()
dialog = MacDialog() # Mixed! ❌
# ✅ Good: Use Abstract Factory to ensure compatibility
factory = UIFactoryProvider.get_factory("windows")
button = factory.create_button()
dialog = factory.create_dialog() # Both Windows - compatible!
over_engineering.py
# ❌ Bad: Using Abstract Factory for independent objects
class SimpleFactory(ABC):
@abstractmethod
def create_a(self): pass
@abstractmethod
def create_b(self): pass # B doesn't need to match A!
# ✅ Better: Use simple Factory Pattern
class SimpleFactory:
def create_a(self):
return A()
def create_b(self):
return B() # Independent creation

  1. Ensures Compatibility - All objects from same factory are compatible
  2. Easy Family Switching - Change factory to switch entire family
  3. Consistency - All objects follow same style/theme
  4. Decoupling - Client doesn’t know concrete classes
  5. Extensibility - Easy to add new families without modifying existing code

Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

  • Families of related objects - Objects that must work together
  • Ensure compatibility - Objects from same family are compatible
  • Easy family switching - Change factory to switch entire family
  • Consistency - All objects follow same style/theme
  • Hide implementation - Client doesn’t know concrete classes
  1. Define abstract products - Interfaces for product types
  2. Define abstract factory - Interface for creating product families
  3. Create concrete products - Implementations for each family
  4. Create concrete factories - Implementations for each family
  5. Use factory - Client uses factory to create compatible products
Client → AbstractFactory → Product Family
  • Abstract Factory - Interface for creating product families
  • Concrete Factory - Creates products for specific family
  • Abstract Products - Interfaces for product types
  • Concrete Products - Implementations for each family
  • Client - Uses factory to create products
class UIFactory(ABC):
@abstractmethod
def create_button(self): pass
@abstractmethod
def create_dialog(self): pass
class WindowsFactory(UIFactory):
def create_button(self):
return WindowsButton()
def create_dialog(self):
return WindowsDialog()
# Usage
factory = WindowsFactory()
button = factory.create_button() # Windows
dialog = factory.create_dialog() # Windows - compatible!

✅ Need families of related objects
✅ Objects must be compatible
✅ Need to switch entire families
✅ Need consistency across objects
✅ Want to hide implementation

❌ Objects are independent
❌ Only one product type
❌ Simple object creation
❌ Over-engineering simple cases

  • Abstract Factory = Creates families of compatible objects
  • Family = Set of related products that work together
  • Compatibility = All products from same factory are compatible
  • Benefit = Ensures consistency and compatibility
  • Use Case = UI frameworks, theme systems, cross-platform apps
class AbstractFactory(ABC):
@abstractmethod
def create_a(self): pass
@abstractmethod
def create_b(self): pass
class Factory1(AbstractFactory):
def create_a(self):
return A1()
def create_b(self):
return B1()
# Usage
factory = Factory1()
a = factory.create_a() # A1
b = factory.create_b() # B1 - compatible!
  • Abstract Factory Pattern creates families of compatible objects
  • It ensures consistency within families
  • Use it when objects must work together
  • Don’t use it for independent object creation
  • It’s more complex than Factory Pattern - use when needed!

What to say:

“Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects. It ensures all objects created by a factory are compatible and work together.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must explain:

  • Factory Pattern: Creates one type of product
  • Abstract Factory Pattern: Creates families of related products
  • Abstract Factory: Ensures compatibility within families

Example to give:

“Factory Pattern creates individual products like ‘create a pizza’. Abstract Factory Pattern creates families like ‘create a complete Italian meal’ - ensuring appetizer, main course, and dessert all match the Italian theme.”

Must mention:

  • Families of related objects - Objects that must work together
  • Ensure compatibility - Objects from same family are compatible
  • Switch families - Easy to switch between different families
  • Consistency - All objects follow same style/theme

Example scenario to give:

“I’d use Abstract Factory Pattern when building a cross-platform UI framework. Each OS (Windows, Mac, Linux) has different UI components. I need to ensure all components in an application match the OS theme - Windows button with Windows dialog, not Mac dialog.”

Benefits to mention:

  • Compatibility - All products from same factory are compatible
  • Consistency - All objects follow same style/theme
  • Easy switching - Change factory to switch entire family
  • Decoupling - Client doesn’t know concrete classes

Trade-offs to acknowledge:

  • Complexity - More complex than Factory Pattern
  • Over-engineering - Can be overkill for simple cases
  • Many classes - Requires many classes (factories + products)

Q: “What’s the difference between Abstract Factory and Factory Pattern?”

A:

“Factory Pattern creates individual products based on input. Abstract Factory Pattern creates families of related products. Factory Pattern: ‘create a pizza’. Abstract Factory Pattern: ‘create a complete Italian meal’ - ensuring all products match.”

Q: “When would you use Abstract Factory vs Factory Pattern?”

A:

“I use Factory Pattern when I need to create individual products - like creating different types of pizzas. I use Abstract Factory Pattern when I need families of compatible objects - like creating UI components that must match the OS theme, or creating complete meals where all items must match the cuisine style.”

Q: “How does Abstract Factory ensure compatibility?”

A:

“Abstract Factory ensures compatibility by having each concrete factory create all products for a specific family. For example, WindowsUIFactory creates WindowsButton, WindowsDialog, and WindowsMenu - all Windows components. The client uses the same factory for all products, guaranteeing they’re from the same family and compatible.”

Before your interview, make sure you can:

  • Define Abstract Factory Pattern clearly in one sentence
  • Distinguish from Factory Pattern
  • Explain when to use it (with examples)
  • Describe how it ensures compatibility
  • Implement Abstract Factory Pattern from scratch
  • Compare with other creational patterns
  • List benefits and trade-offs
  • Identify common mistakes
  • Give 2-3 real-world examples
  • Discuss when NOT to use it

Remember: Abstract Factory Pattern creates families of compatible objects - ensuring consistency and compatibility within each family! 🏭