Abstract Factory Pattern
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.
Why Abstract Factory Pattern?
Section titled “Why Abstract Factory Pattern?”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:
- 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
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
Simple Example: The Pizza Meal Factory
Section titled “Simple Example: The Pizza Meal Factory”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”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!
The Problem
Section titled “The Problem”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:
# ❌ 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// ❌ Without Abstract Factory Pattern - Can create incompatible objects!
public class ItalianAppetizer { public String serve() { return "🍞 Serving Bruschetta"; }}
public class ItalianMain { public String serve() { return "🍕 Serving Margherita Pizza"; }}
public class ItalianDessert { public String serve() { return "🍰 Serving Tiramisu"; }}
public class AmericanAppetizer { public String serve() { return "🍞 Serving Garlic Bread"; }}
public class AmericanMain { public String serve() { return "🍕 Serving Pepperoni Pizza"; }}
public class AmericanDessert { public String serve() { return "🍰 Serving Cheesecake"; }}
// Problem: Can accidentally mix incompatible objects!public class MealService { public static List<Object> createMeal(String style) { if ("italian".equals(style)) { return Arrays.asList( new ItalianAppetizer(), new ItalianMain(), new ItalianDessert() ); } else if ("american".equals(style)) { return Arrays.asList( new AmericanAppetizer(), new AmericanMain(), new AmericanDessert() ); } else { throw new IllegalArgumentException("Unknown style"); } }}
// Problem: Easy to make mistakes!List<Object> meal1 = MealService.createMeal("italian");List<Object> meal2 = Arrays.asList( new ItalianAppetizer(), new AmericanMain(), // Mixed! ❌ new ItalianDessert());
// Problems:// - Can mix objects from different families// - Creation logic scattered// - Hard to ensure consistency// - Need to modify code to add new familiesProblems:
- Can mix incompatible objects from different families
- Creation logic scattered
- Hard to ensure consistency
- Need to modify code to add new families
The Solution: Abstract Factory Pattern
Section titled “The Solution: Abstract Factory Pattern”Class Structure
Section titled “Class Structure”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"
from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define abstract product interfacesclass 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 factoryclass 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 familyclass 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 familyclass 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 factoriesclass 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 patterndef 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() ]
# Usagedef 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()import java.util.*;
// Step 1: Define abstract product interfacesinterface Appetizer { String serve();}
interface MainCourse { String serve();}
interface Dessert { String serve();}
// Step 2: Define abstract factoryinterface PizzaMealFactory { // Abstract Factory - creates families of related products Appetizer createAppetizer(); MainCourse createMain(); Dessert createDessert();}
// Step 3: Create concrete products for Italian familyclass ItalianAppetizer implements Appetizer { @Override public String serve() { return "🍞 Serving Bruschetta"; }}
class ItalianMain implements MainCourse { @Override public String serve() { return "🍕 Serving Margherita Pizza"; }}
class ItalianDessert implements Dessert { @Override public String serve() { return "🍰 Serving Tiramisu"; }}
// Step 4: Create concrete products for American familyclass AmericanAppetizer implements Appetizer { @Override public String serve() { return "🍞 Serving Garlic Bread"; }}
class AmericanMain implements MainCourse { @Override public String serve() { return "🍕 Serving Pepperoni Pizza"; }}
class AmericanDessert implements Dessert { @Override public String serve() { return "🍰 Serving Cheesecake"; }}
// Step 5: Create concrete factoriesclass ItalianPizzaMealFactory implements PizzaMealFactory { // Concrete Factory - creates Italian meal family @Override public Appetizer createAppetizer() { return new ItalianAppetizer(); }
@Override public MainCourse createMain() { return new ItalianMain(); }
@Override public Dessert createDessert() { return new ItalianDessert(); }}
class AmericanPizzaMealFactory implements PizzaMealFactory { // Concrete Factory - creates American meal family @Override public Appetizer createAppetizer() { return new AmericanAppetizer(); }
@Override public MainCourse createMain() { return new AmericanMain(); }
@Override public Dessert createDessert() { return new AmericanDessert(); }}
// Step 6: Factory provider (optional)class MealFactoryProvider { public static PizzaMealFactory getFactory(String style) { if ("italian".equalsIgnoreCase(style)) { return new ItalianPizzaMealFactory(); } else if ("american".equalsIgnoreCase(style)) { return new AmericanPizzaMealFactory(); } else { throw new IllegalArgumentException("Unknown meal style: " + style); } }}
// Step 7: Use the patternpublic class Main { public static List<String> createCompleteMeal(PizzaMealFactory factory) { // Create a complete meal using the factory Appetizer appetizer = factory.createAppetizer(); MainCourse main = factory.createMain(); Dessert dessert = factory.createDessert();
return Arrays.asList( appetizer.serve(), main.serve(), dessert.serve() ); }
public static void main(String[] args) { // Create Italian meal - all items are Italian! PizzaMealFactory italianFactory = MealFactoryProvider.getFactory("italian"); List<String> italianMeal = createCompleteMeal(italianFactory); System.out.println("🇮🇹 Italian Meal:"); for (String item : italianMeal) { System.out.println(" " + item); }
System.out.println();
// Create American meal - all items are American! PizzaMealFactory americanFactory = MealFactoryProvider.getFactory("american"); List<String> americanMeal = createCompleteMeal(americanFactory); System.out.println("🇺🇸 American Meal:"); for (String item : americanMeal) { System.out.println(" " + item); }
// Can't mix families - factory ensures consistency! // italianFactory.createAppetizer() + americanFactory.createMain() ❌ }}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.
The Problem
Section titled “The Problem”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:
# ❌ 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// ❌ Without Abstract Factory Pattern - Can create incompatible UI components!
public class WindowsButton { public String render() { return "🪟 Windows Button"; }}
public class WindowsDialog { public String render() { return "🪟 Windows Dialog"; }}
public class MacButton { public String render() { return "🍎 Mac Button"; }}
public class MacDialog { public String render() { return "🍎 Mac Dialog"; }}
// Problem: Can accidentally mix incompatible components!public class UIService { public static List<Object> createUI(String os) { if ("windows".equals(os)) { return Arrays.asList(new WindowsButton(), new WindowsDialog()); } else if ("mac".equals(os)) { return Arrays.asList(new MacButton(), new MacDialog()); } else { throw new IllegalArgumentException("Unknown OS"); } }}
// Problem: Easy to make mistakes!List<Object> ui1 = UIService.createUI("windows");List<Object> ui2 = Arrays.asList( new WindowsButton(), new MacDialog() // Mixed! ❌);
// Problems:// - Can mix components from different OS// - Creation logic scattered// - Hard to ensure consistency// - Need to modify code to add new OSProblems:
- Can mix incompatible UI components from different OS
- Creation logic scattered
- Hard to ensure consistency
- Need to modify code to add new OS
The Solution: Abstract Factory Pattern
Section titled “The Solution: Abstract Factory Pattern”from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define abstract product interfacesclass 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 factoryclass 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 familyclass 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 familyclass 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 factoriesclass 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 providerclass 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 patterndef 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() ]
# Usagedef 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()import java.util.*;
// Step 1: Define abstract product interfacesinterface Button { String render();}
interface Dialog { String render();}
interface Menu { String render();}
// Step 2: Define abstract factoryinterface UIFactory { // Abstract Factory - creates families of UI components Button createButton(); Dialog createDialog(); Menu createMenu();}
// Step 3: Create concrete products for Windows familyclass WindowsButton implements Button { @Override public String render() { return "🪟 Windows Button"; }}
class WindowsDialog implements Dialog { @Override public String render() { return "🪟 Windows Dialog"; }}
class WindowsMenu implements Menu { @Override public String render() { return "🪟 Windows Menu"; }}
// Step 4: Create concrete products for Mac familyclass MacButton implements Button { @Override public String render() { return "🍎 Mac Button"; }}
class MacDialog implements Dialog { @Override public String render() { return "🍎 Mac Dialog"; }}
class MacMenu implements Menu { @Override public String render() { return "🍎 Mac Menu"; }}
// Step 5: Create concrete factoriesclass WindowsUIFactory implements UIFactory { // Concrete Factory - creates Windows UI component family @Override public Button createButton() { return new WindowsButton(); }
@Override public Dialog createDialog() { return new WindowsDialog(); }
@Override public Menu createMenu() { return new WindowsMenu(); }}
class MacUIFactory implements UIFactory { // Concrete Factory - creates Mac UI component family @Override public Button createButton() { return new MacButton(); }
@Override public Dialog createDialog() { return new MacDialog(); }
@Override public Menu createMenu() { return new MacMenu(); }}
// Step 6: Factory providerclass UIFactoryProvider { public static UIFactory getFactory(String os) { if ("windows".equalsIgnoreCase(os)) { return new WindowsUIFactory(); } else if ("mac".equalsIgnoreCase(os)) { return new MacUIFactory(); } else { throw new IllegalArgumentException("Unknown OS: " + os); } }}
// Step 7: Use the patternpublic class Main { public static List<String> createApplicationUI(UIFactory factory) { // Create application UI using the factory Button button = factory.createButton(); Dialog dialog = factory.createDialog(); Menu menu = factory.createMenu();
return Arrays.asList( button.render(), dialog.render(), menu.render() ); }
public static void main(String[] args) { // Detect OS (in real app, this would be actual OS detection) String currentOS = "windows"; // or "mac"
// Create UI factory for current OS UIFactory uiFactory = UIFactoryProvider.getFactory(currentOS);
// Create application UI - all components match the OS! List<String> uiComponents = createApplicationUI(uiFactory);
System.out.println("🖥️ Application UI (" + currentOS.toUpperCase() + "):"); for (String component : uiComponents) { System.out.println(" " + component); }
// All components are from the same OS family - guaranteed consistency! }}Abstract Factory Pattern Variants
Section titled “Abstract Factory Pattern Variants”There are several ways to implement the Abstract Factory Pattern:
1. Simple Abstract Factory
Section titled “1. Simple Abstract Factory”Basic implementation with abstract factory interface:
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()interface AbstractFactory { ProductA createProductA(); ProductB createProductB();}
class ConcreteFactory1 implements AbstractFactory { @Override public ProductA createProductA() { return new ProductA1(); }
@Override public ProductB createProductB() { return new ProductB1(); }}2. Factory Provider Pattern
Section titled “2. Factory Provider Pattern”Add a provider to get the right factory:
class FactoryProvider: @staticmethod def get_factory(type: str) -> AbstractFactory: if type == "type1": return ConcreteFactory1() elif type == "type2": return ConcreteFactory2() else: raise ValueError("Unknown type")class FactoryProvider { public static AbstractFactory getFactory(String type) { if ("type1".equals(type)) { return new ConcreteFactory1(); } else if ("type2".equals(type)) { return new ConcreteFactory2(); } else { throw new IllegalArgumentException("Unknown type"); } }}When to Use Abstract Factory Pattern?
Section titled “When to Use Abstract Factory Pattern?”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
When NOT to Use Abstract Factory Pattern?
Section titled “When NOT to Use Abstract Factory Pattern?”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
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Mixing Products from Different Families
Section titled “Mistake 1: Mixing Products from Different Families”# ❌ Bad: Mixing products from different familiesfactory1 = ItalianFactory()factory2 = AmericanFactory()
meal = [ factory1.create_appetizer(), # Italian factory2.create_main() # American - incompatible!]
# ✅ Good: Use same factory for all productsfactory = ItalianFactory()meal = [ factory.create_appetizer(), # Italian factory.create_main() # Italian - compatible!]// ❌ Bad: Mixing products from different familiesUIFactory factory1 = new WindowsUIFactory();UIFactory factory2 = new MacUIFactory();
List<Object> ui = Arrays.asList( factory1.createButton(), // Windows factory2.createDialog() // Mac - incompatible!);
// ✅ Good: Use same factory for all productsUIFactory factory = new WindowsUIFactory();List<Object> ui = Arrays.asList( factory.createButton(), // Windows factory.createDialog() // Windows - compatible!);Mistake 2: Not Using Abstract Factory When Needed
Section titled “Mistake 2: Not Using Abstract Factory When Needed”# ❌ Bad: Creating objects individually - can mix incompatible onesbutton = WindowsButton()dialog = MacDialog() # Mixed! ❌
# ✅ Good: Use Abstract Factory to ensure compatibilityfactory = UIFactoryProvider.get_factory("windows")button = factory.create_button()dialog = factory.create_dialog() # Both Windows - compatible!// ❌ Bad: Creating objects individually - can mix incompatible onesButton button = new WindowsButton();Dialog dialog = new MacDialog(); // Mixed! ❌
// ✅ Good: Use Abstract Factory to ensure compatibilityUIFactory factory = UIFactoryProvider.getFactory("windows");Button button = factory.createButton();Dialog dialog = factory.createDialog(); // Both Windows - compatible!Mistake 3: Over-Engineering Simple Cases
Section titled “Mistake 3: Over-Engineering Simple Cases”# ❌ Bad: Using Abstract Factory for independent objectsclass 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 Patternclass SimpleFactory: def create_a(self): return A()
def create_b(self): return B() # Independent creation// ❌ Bad: Using Abstract Factory for independent objectsinterface SimpleFactory { A createA(); B createB(); // B doesn't need to match A!}
// ✅ Better: Use simple Factory Patternclass SimpleFactory { public A createA() { return new A(); }
public B createB() { return new B(); // Independent creation }}Benefits of Abstract Factory Pattern
Section titled “Benefits of Abstract Factory Pattern”- Ensures Compatibility - All objects from same factory are compatible
- Easy Family Switching - Change factory to switch entire family
- Consistency - All objects follow same style/theme
- Decoupling - Client doesn’t know concrete classes
- Extensibility - Easy to add new families without modifying existing code
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Abstract Factory Pattern?
Section titled “What is Abstract Factory Pattern?”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.
Why Use It?
Section titled “Why Use It?”- ✅ 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
How It Works?
Section titled “How It Works?”- Define abstract products - Interfaces for product types
- Define abstract factory - Interface for creating product families
- Create concrete products - Implementations for each family
- Create concrete factories - Implementations for each family
- Use factory - Client uses factory to create compatible products
Key Components
Section titled “Key Components”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
Simple Example
Section titled “Simple Example”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()
# Usagefactory = WindowsFactory()button = factory.create_button() # Windowsdialog = factory.create_dialog() # Windows - compatible!When to Use?
Section titled “When to Use?”✅ Need families of related objects
✅ Objects must be compatible
✅ Need to switch entire families
✅ Need consistency across objects
✅ Want to hide implementation
When NOT to Use?
Section titled “When NOT to Use?”❌ Objects are independent
❌ Only one product type
❌ Simple object creation
❌ Over-engineering simple cases
Key Takeaways
Section titled “Key Takeaways”- 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
Common Pattern Structure
Section titled “Common Pattern Structure”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()
# Usagefactory = Factory1()a = factory.create_a() # A1b = factory.create_b() # B1 - compatible!Remember
Section titled “Remember”- 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!
Interview Focus: Abstract Factory Pattern
Section titled “Interview Focus: Abstract Factory Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”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
2. Difference from Factory Pattern
Section titled “2. Difference from Factory Pattern”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.”
3. When to Use Abstract Factory Pattern
Section titled “3. When to Use Abstract Factory Pattern”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.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”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)
5. Common Interview Questions
Section titled “5. Common Interview Questions”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.”
Interview Checklist
Section titled “Interview Checklist”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! 🏭