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:
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 families// ❌ Without Abstract Factory Pattern - Can create incompatible objects!
class ItalianAppetizer { serve(): string { return "🍞 Serving Bruschetta"; }}
class ItalianMain { serve(): string { return "🍕 Serving Margherita Pizza"; }}
class ItalianDessert { serve(): string { return "🍰 Serving Tiramisu"; }}
class AmericanAppetizer { serve(): string { return "🍞 Serving Garlic Bread"; }}
class AmericanMain { serve(): string { return "🍕 Serving Pepperoni Pizza"; }}
class AmericanDessert { serve(): string { return "🍰 Serving Cheesecake"; }}
// Problem: Can accidentally mix incompatible objects!function createMeal(style: string): any[] { if (style === "italian") { return [ new ItalianAppetizer(), new ItalianMain(), new ItalianDessert() ]; } else if (style === "american") { return [ new AmericanAppetizer(), new AmericanMain(), new AmericanDessert() ]; } else { throw new Error("Unknown style"); }}
// Problem: Easy to make mistakes!const meal1 = createMeal("italian");const meal2 = [new ItalianAppetizer(), new AmericanMain(), new 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!
#include <string>#include <vector>#include <stdexcept>
class ItalianAppetizer {public: std::string serve() { return "🍞 Serving Bruschetta"; }};
class ItalianMain {public: std::string serve() { return "🍕 Serving Margherita Pizza"; }};
class ItalianDessert {public: std::string serve() { return "🍰 Serving Tiramisu"; }};
class AmericanAppetizer {public: std::string serve() { return "🍞 Serving Garlic Bread"; }};
class AmericanMain {public: std::string serve() { return "🍕 Serving Pepperoni Pizza"; }};
class AmericanDessert {public: std::string serve() { return "🍰 Serving Cheesecake"; }};
// Problem: Can accidentally mix incompatible objects!// Note: Using void* as a simplified example - not recommended in productionstd::vector<void*> createMeal(const std::string& style) { std::vector<void*> meal;
if (style == "italian") { meal.push_back(new ItalianAppetizer()); meal.push_back(new ItalianMain()); meal.push_back(new ItalianDessert()); } else if (style == "american") { meal.push_back(new AmericanAppetizer()); meal.push_back(new AmericanMain()); meal.push_back(new AmericanDessert()); } else { throw std::invalid_argument("Unknown style"); }
return meal;}
// Problem: Easy to make mistakes!auto meal1 = createMeal("italian");std::vector<void*> meal2 = { 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 families// ❌ Without Abstract Factory Pattern - Can create incompatible objects!
using System;using System.Collections.Generic;
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 (style == "italian") { return new List<object> { new ItalianAppetizer(), new ItalianMain(), new ItalianDessert() }; } else if (style == "american") { return new List<object> { new AmericanAppetizer(), new AmericanMain(), new AmericanDessert() }; } else { throw new ArgumentException("Unknown style"); } }}
// Problem: Easy to make mistakes!List<object> meal1 = MealService.CreateMeal("italian");List<object> meal2 = new List<object>{ 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 families// ❌ Without Abstract Factory Pattern - Can create incompatible objects!
package main
import ( "fmt")
type ItalianAppetizer struct{}
func (ItalianAppetizer) Serve() string { return "🍞 Serving Bruschetta" }
type ItalianMain struct{}
func (ItalianMain) Serve() string { return "🍕 Serving Margherita Pizza" }
type ItalianDessert struct{}
func (ItalianDessert) Serve() string { return "🍰 Serving Tiramisu" }
type AmericanAppetizer struct{}
func (AmericanAppetizer) Serve() string { return "🍞 Serving Garlic Bread" }
type AmericanMain struct{}
func (AmericanMain) Serve() string { return "🍕 Serving Pepperoni Pizza" }
type AmericanDessert struct{}
func (AmericanDessert) Serve() string { return "🍰 Serving Cheesecake" }
// Problem: Can accidentally mix incompatible objects!func CreateMeal(style string) ([]any, error) { switch style { case "italian": return []any{ItalianAppetizer{}, ItalianMain{}, ItalianDessert{}}, nil case "american": return []any{AmericanAppetizer{}, AmericanMain{}, AmericanDessert{}}, nil default: return nil, fmt.Errorf("unknown style") }}
// Problem: Easy to make mistakes!func demo() { meal1, _ := CreateMeal("italian") _ = meal1 meal2 := []any{ ItalianAppetizer{}, AmericanMain{}, // Mixed! ❌ ItalianDessert{}, } _ = meal2}
// Problems:// - Can mix objects from different families// - Creation logic scattered// - Hard to ensure consistency// - Need to modify code to add new families// The Problemstruct App;impl App { fn render_windows_ui(&self) { println!("Windows button"); println!("Windows checkbox"); } fn render_mac_ui(&self) { println!("Mac button"); println!("Mac checkbox"); }}Problems:
- 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”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() ❌ }}// Step 1: Define abstract product interfacesinterface Appetizer { /** Abstract product: Appetizer */ serve(): string;}
interface MainCourse { /** Abstract product: Main Course */ serve(): string;}
interface Dessert { /** Abstract product: Dessert */ serve(): string;}
// Step 2: Define abstract factoryinterface PizzaMealFactory { /** Abstract Factory - creates families of related products */ createAppetizer(): Appetizer; createMain(): MainCourse; createDessert(): Dessert;}
// Step 3: Create concrete products for Italian familyclass ItalianAppetizer implements Appetizer { serve(): string { return "🍞 Serving Bruschetta"; }}
class ItalianMain implements MainCourse { serve(): string { return "🍕 Serving Margherita Pizza"; }}
class ItalianDessert implements Dessert { serve(): string { return "🍰 Serving Tiramisu"; }}
// Step 4: Create concrete products for American familyclass AmericanAppetizer implements Appetizer { serve(): string { return "🍞 Serving Garlic Bread"; }}
class AmericanMain implements MainCourse { serve(): string { return "🍕 Serving Pepperoni Pizza"; }}
class AmericanDessert implements Dessert { serve(): string { return "🍰 Serving Cheesecake"; }}
// Step 5: Create concrete factoriesclass ItalianPizzaMealFactory implements PizzaMealFactory { /** Concrete Factory - creates Italian meal family */
createAppetizer(): Appetizer { return new ItalianAppetizer(); }
createMain(): MainCourse { return new ItalianMain(); }
createDessert(): Dessert { return new ItalianDessert(); }}
class AmericanPizzaMealFactory implements PizzaMealFactory { /** Concrete Factory - creates American meal family */
createAppetizer(): Appetizer { return new AmericanAppetizer(); }
createMain(): MainCourse { return new AmericanMain(); }
createDessert(): Dessert { return new AmericanDessert(); }}
// Step 6: Factory provider (optional)class MealFactoryProvider { /** Provides the appropriate factory */
static getFactory(style: string): PizzaMealFactory { if (style.toLowerCase() === "italian") { return new ItalianPizzaMealFactory(); } else if (style.toLowerCase() === "american") { return new AmericanPizzaMealFactory(); } else { throw new Error(`Unknown meal style: ${style}`); } }}
// Step 7: Use the patternfunction createCompleteMeal(factory: PizzaMealFactory): string[] { /** Create a complete meal using the factory */ const appetizer = factory.createAppetizer(); const main = factory.createMain(); const dessert = factory.createDessert();
return [ appetizer.serve(), main.serve(), dessert.serve() ];}
// Usagefunction main(): void { // Create Italian meal - all items are Italian! const italianFactory = MealFactoryProvider.getFactory("italian"); const italianMeal = createCompleteMeal(italianFactory); console.log("🇮🇹 Italian Meal:"); italianMeal.forEach(item => console.log(` ${item}`));
console.log();
// Create American meal - all items are American! const americanFactory = MealFactoryProvider.getFactory("american"); const americanMeal = createCompleteMeal(americanFactory); console.log("🇺🇸 American Meal:"); americanMeal.forEach(item => console.log(` ${item}`));
// Can't mix families - factory ensures consistency! // italianFactory.createAppetizer() + americanFactory.createMain() ❌}
main();#include <string>#include <memory>#include <vector>#include <stdexcept>#include <iostream>
// Step 1: Define abstract product interfacesclass Appetizer {public: virtual ~Appetizer() = default; virtual std::string serve() = 0;};
class MainCourse {public: virtual ~MainCourse() = default; virtual std::string serve() = 0;};
class Dessert {public: virtual ~Dessert() = default; virtual std::string serve() = 0;};
// Step 2: Define abstract factoryclass PizzaMealFactory {public: virtual ~PizzaMealFactory() = default; // Abstract Factory - creates families of related products virtual std::unique_ptr<Appetizer> createAppetizer() = 0; virtual std::unique_ptr<MainCourse> createMain() = 0; virtual std::unique_ptr<Dessert> createDessert() = 0;};
// Step 3: Create concrete products for Italian familyclass ItalianAppetizer : public Appetizer {public: std::string serve() override { return "🍞 Serving Bruschetta"; }};
class ItalianMain : public MainCourse {public: std::string serve() override { return "🍕 Serving Margherita Pizza"; }};
class ItalianDessert : public Dessert {public: std::string serve() override { return "🍰 Serving Tiramisu"; }};
// Step 4: Create concrete products for American familyclass AmericanAppetizer : public Appetizer {public: std::string serve() override { return "🍞 Serving Garlic Bread"; }};
class AmericanMain : public MainCourse {public: std::string serve() override { return "🍕 Serving Pepperoni Pizza"; }};
class AmericanDessert : public Dessert {public: std::string serve() override { return "🍰 Serving Cheesecake"; }};
// Step 5: Create concrete factoriesclass ItalianPizzaMealFactory : public PizzaMealFactory {public: // Concrete Factory - creates Italian meal family std::unique_ptr<Appetizer> createAppetizer() override { return std::make_unique<ItalianAppetizer>(); }
std::unique_ptr<MainCourse> createMain() override { return std::make_unique<ItalianMain>(); }
std::unique_ptr<Dessert> createDessert() override { return std::make_unique<ItalianDessert>(); }};
class AmericanPizzaMealFactory : public PizzaMealFactory {public: // Concrete Factory - creates American meal family std::unique_ptr<Appetizer> createAppetizer() override { return std::make_unique<AmericanAppetizer>(); }
std::unique_ptr<MainCourse> createMain() override { return std::make_unique<AmericanMain>(); }
std::unique_ptr<Dessert> createDessert() override { return std::make_unique<AmericanDessert>(); }};
// Step 6: Factory provider (optional)class MealFactoryProvider {public: static std::unique_ptr<PizzaMealFactory> getFactory(const std::string& style) { std::string styleLower = style; for (char& c : styleLower) c = std::tolower(c);
if (styleLower == "italian") { return std::make_unique<ItalianPizzaMealFactory>(); } else if (styleLower == "american") { return std::make_unique<AmericanPizzaMealFactory>(); } else { throw std::invalid_argument("Unknown meal style: " + style); } }};
// Step 7: Use the patternstd::vector<std::string> createCompleteMeal(PizzaMealFactory& factory) { // Create a complete meal using the factory auto appetizer = factory.createAppetizer(); auto main = factory.createMain(); auto dessert = factory.createDessert();
return { appetizer->serve(), main->serve(), dessert->serve() };}
// Usageint main() { // Create Italian meal - all items are Italian! auto italianFactory = MealFactoryProvider::getFactory("italian"); auto italianMeal = createCompleteMeal(*italianFactory); std::cout << "🇮🇹 Italian Meal:" << std::endl; for (const auto& item : italianMeal) { std::cout << " " << item << std::endl; }
std::cout << std::endl;
// Create American meal - all items are American! auto americanFactory = MealFactoryProvider::getFactory("american"); auto americanMeal = createCompleteMeal(*americanFactory); std::cout << "🇺🇸 American Meal:" << std::endl; for (const auto& item : americanMeal) { std::cout << " " << item << std::endl; }
// Can't mix families - factory ensures consistency! // italianFactory->createAppetizer() + americanFactory->createMain() ❌
return 0;}using System;using System.Collections.Generic;
// Step 1: Define abstract product interfacespublic interface IAppetizer{ string Serve();}
public interface IMainCourse{ string Serve();}
public interface IDessert{ string Serve();}
// Step 2: Define abstract factorypublic interface IPizzaMealFactory{ // Abstract Factory - creates families of related products IAppetizer CreateAppetizer(); IMainCourse CreateMain(); IDessert CreateDessert();}
// Step 3: Create concrete products for Italian familypublic class ItalianAppetizer : IAppetizer{ public string Serve() { return "🍞 Serving Bruschetta"; }}
public class ItalianMain : IMainCourse{ public string Serve() { return "🍕 Serving Margherita Pizza"; }}
public class ItalianDessert : IDessert{ public string Serve() { return "🍰 Serving Tiramisu"; }}
// Step 4: Create concrete products for American familypublic class AmericanAppetizer : IAppetizer{ public string Serve() { return "🍞 Serving Garlic Bread"; }}
public class AmericanMain : IMainCourse{ public string Serve() { return "🍕 Serving Pepperoni Pizza"; }}
public class AmericanDessert : IDessert{ public string Serve() { return "🍰 Serving Cheesecake"; }}
// Step 5: Create concrete factoriespublic class ItalianPizzaMealFactory : IPizzaMealFactory{ // Concrete Factory - creates Italian meal family public IAppetizer CreateAppetizer() { return new ItalianAppetizer(); }
public IMainCourse CreateMain() { return new ItalianMain(); }
public IDessert CreateDessert() { return new ItalianDessert(); }}
public class AmericanPizzaMealFactory : IPizzaMealFactory{ // Concrete Factory - creates American meal family public IAppetizer CreateAppetizer() { return new AmericanAppetizer(); }
public IMainCourse CreateMain() { return new AmericanMain(); }
public IDessert CreateDessert() { return new AmericanDessert(); }}
// Step 6: Factory provider (optional)public class MealFactoryProvider{ public static IPizzaMealFactory GetFactory(string style) { if (style.ToLower() == "italian") { return new ItalianPizzaMealFactory(); } else if (style.ToLower() == "american") { return new AmericanPizzaMealFactory(); } else { throw new ArgumentException($"Unknown meal style: {style}"); } }}
// Step 7: Use the patternpublic class Program{ public static List<string> CreateCompleteMeal(IPizzaMealFactory factory) { // Create a complete meal using the factory IAppetizer appetizer = factory.CreateAppetizer(); IMainCourse main = factory.CreateMain(); IDessert dessert = factory.CreateDessert();
return new List<string> { appetizer.Serve(), main.Serve(), dessert.Serve() }; }
static void Main() { // Create Italian meal - all items are Italian! IPizzaMealFactory italianFactory = MealFactoryProvider.GetFactory("italian"); List<string> italianMeal = CreateCompleteMeal(italianFactory); Console.WriteLine("🇮🇹 Italian Meal:"); foreach (string item in italianMeal) { Console.WriteLine($" {item}"); }
Console.WriteLine();
// Create American meal - all items are American! IPizzaMealFactory americanFactory = MealFactoryProvider.GetFactory("american"); List<string> americanMeal = CreateCompleteMeal(americanFactory); Console.WriteLine("🇺🇸 American Meal:"); foreach (string item in americanMeal) { Console.WriteLine($" {item}"); }
// Can't mix families - factory ensures consistency! // italianFactory.CreateAppetizer() + americanFactory.CreateMain() ❌ }}package main
import ( "fmt" "strings")
// Step 1type Appetizer interface{ Serve() string }type MainCourse interface{ Serve() string }type Dessert interface{ Serve() string }
// Step 2 & 3 — productstype ItalianAppetizer struct{}
func (ItalianAppetizer) Serve() string { return "🍞 Serving Bruschetta" }
type ItalianMain struct{}
func (ItalianMain) Serve() string { return "🍕 Serving Margherita Pizza" }
type ItalianDessert struct{}
func (ItalianDessert) Serve() string { return "🍰 Serving Tiramisu" }
type AmericanAppetizer struct{}
func (AmericanAppetizer) Serve() string { return "🍞 Serving Garlic Bread" }
type AmericanMain struct{}
func (AmericanMain) Serve() string { return "🍕 Serving Pepperoni Pizza" }
type AmericanDessert struct{}
func (AmericanDessert) Serve() string { return "🍰 Serving Cheesecake" }
// Step 4 — abstract factorytype PizzaMealFactory interface { CreateAppetizer() Appetizer CreateMain() MainCourse CreateDessert() Dessert}
type ItalianPizzaMealFactory struct{}
func (ItalianPizzaMealFactory) CreateAppetizer() Appetizer { return ItalianAppetizer{} }func (ItalianPizzaMealFactory) CreateMain() MainCourse { return ItalianMain{} }func (ItalianPizzaMealFactory) CreateDessert() Dessert { return ItalianDessert{} }
type AmericanPizzaMealFactory struct{}
func (AmericanPizzaMealFactory) CreateAppetizer() Appetizer { return AmericanAppetizer{} }func (AmericanPizzaMealFactory) CreateMain() MainCourse { return AmericanMain{} }func (AmericanPizzaMealFactory) CreateDessert() Dessert { return AmericanDessert{} }
// Step 5 — factory providertype MealFactoryProvider struct{}
func (MealFactoryProvider) GetFactory(style string) (PizzaMealFactory, error) { switch strings.ToLower(style) { case "italian": return ItalianPizzaMealFactory{}, nil case "american": return AmericanPizzaMealFactory{}, nil default: return nil, fmt.Errorf("unknown meal style: %s", style) }}
// Step 6 — use patternfunc CreateCompleteMeal(factory PizzaMealFactory) []string { a := factory.CreateAppetizer() m := factory.CreateMain() d := factory.CreateDessert() return []string{a.Serve(), m.Serve(), d.Serve()}}
func main() { p := MealFactoryProvider{} it, err := p.GetFactory("italian") if err != nil { panic(err) } fmt.Println("🇮🇹 Italian Meal:") for _, item := range CreateCompleteMeal(it) { fmt.Printf(" %s\n", item) } fmt.Println() am, err := p.GetFactory("american") if err != nil { panic(err) } fmt.Println("🇺🇸 American Meal:") for _, item := range CreateCompleteMeal(am) { fmt.Printf(" %s\n", item) } // italianFactory.CreateAppetizer + americanFactory.CreateMain ❌ — don't mix factories}// Class Structuretrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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 OS// ❌ Without Abstract Factory Pattern - Can create incompatible UI components!
class WindowsButton { render(): string { return "🪟 Windows Button"; }}
class WindowsDialog { render(): string { return "🪟 Windows Dialog"; }}
class MacButton { render(): string { return "🍎 Mac Button"; }}
class MacDialog { render(): string { return "🍎 Mac Dialog"; }}
// Problem: Can accidentally mix incompatible components!function createUI(os: string): any[] { if (os === "windows") { return [new WindowsButton(), new WindowsDialog()]; } else if (os === "mac") { return [new MacButton(), new MacDialog()]; } else { throw new Error("Unknown OS"); }}
// Problem: Easy to make mistakes!const ui1 = createUI("windows");const ui2 = [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 OS// ❌ Without Abstract Factory Pattern - Can create incompatible UI components!
#include <string>#include <vector>#include <stdexcept>
class WindowsButton {public: std::string render() { return "🪟 Windows Button"; }};
class WindowsDialog {public: std::string render() { return "🪟 Windows Dialog"; }};
class MacButton {public: std::string render() { return "🍎 Mac Button"; }};
class MacDialog {public: std::string render() { return "🍎 Mac Dialog"; }};
// Problem: Can accidentally mix incompatible components!std::vector<void*> createUI(const std::string& os) { std::vector<void*> ui;
if (os == "windows") { ui.push_back(new WindowsButton()); ui.push_back(new WindowsDialog()); } else if (os == "mac") { ui.push_back(new MacButton()); ui.push_back(new MacDialog()); } else { throw std::invalid_argument("Unknown OS"); }
return ui;}
// Problem: Easy to make mistakes!auto ui1 = createUI("windows");std::vector<void*> ui2 = { 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 OS// ❌ Without Abstract Factory Pattern - Can create incompatible UI components!
using System;using System.Collections.Generic;
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 (os == "windows") { return new List<object> { new WindowsButton(), new WindowsDialog() }; } else if (os == "mac") { return new List<object> { new MacButton(), new MacDialog() }; } else { throw new ArgumentException("Unknown OS"); } }}
// Problem: Easy to make mistakes!List<object> ui1 = UIService.CreateUI("windows");List<object> ui2 = new List<object>{ 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 OS// ❌ Without Abstract Factory Pattern — can create incompatible UI components
package main
import ( "fmt")
type WindowsButton struct{}
func (WindowsButton) Render() string { return "🪟 Windows Button" }
type WindowsDialog struct{}
func (WindowsDialog) Render() string { return "🪟 Windows Dialog" }
type MacButton struct{}
func (MacButton) Render() string { return "🍎 Mac Button" }
type MacDialog struct{}
func (MacDialog) Render() string { return "🍎 Mac Dialog" }
func CreateUI(osName string) ([]any, error) { switch osName { case "windows": return []any{WindowsButton{}, WindowsDialog{}}, nil case "mac": return []any{MacButton{}, MacDialog{}}, nil default: return nil, fmt.Errorf("unknown OS") }}
func demoBadUI() { _, _ = CreateUI("windows") ui2 := []any{ WindowsButton{}, MacDialog{}, // Mixed! ❌ } _ = ui2}
// Problems:// - Can mix components from different OS// - Creation logic scattered// - Hard to ensure consistency// - Need to modify code to add new OS// The Problemstruct App;impl App { fn render_windows_ui(&self) { println!("Windows button"); println!("Windows checkbox"); } fn render_mac_ui(&self) { println!("Mac button"); println!("Mac checkbox"); }}Problems:
- 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! }}// Step 1: Define abstract product interfacesinterface Button { /** Abstract product: Button */ render(): string;}
interface Dialog { /** Abstract product: Dialog */ render(): string;}
interface Menu { /** Abstract product: Menu */ render(): string;}
// Step 2: Define abstract factoryinterface UIFactory { /** Abstract Factory - creates families of UI components */ createButton(): Button; createDialog(): Dialog; createMenu(): Menu;}
// Step 3: Create concrete products for Windows familyclass WindowsButton implements Button { render(): string { return "🪟 Windows Button"; }}
class WindowsDialog implements Dialog { render(): string { return "🪟 Windows Dialog"; }}
class WindowsMenu implements Menu { render(): string { return "🪟 Windows Menu"; }}
// Step 4: Create concrete products for Mac familyclass MacButton implements Button { render(): string { return "🍎 Mac Button"; }}
class MacDialog implements Dialog { render(): string { return "🍎 Mac Dialog"; }}
class MacMenu implements Menu { render(): string { return "🍎 Mac Menu"; }}
// Step 5: Create concrete factoriesclass WindowsUIFactory implements UIFactory { /** Concrete Factory - creates Windows UI component family */
createButton(): Button { return new WindowsButton(); }
createDialog(): Dialog { return new WindowsDialog(); }
createMenu(): Menu { return new WindowsMenu(); }}
class MacUIFactory implements UIFactory { /** Concrete Factory - creates Mac UI component family */
createButton(): Button { return new MacButton(); }
createDialog(): Dialog { return new MacDialog(); }
createMenu(): Menu { return new MacMenu(); }}
// Step 6: Factory providerclass UIFactoryProvider { /** Provides the appropriate UI factory based on OS */
static getFactory(os: string): UIFactory { if (os.toLowerCase() === "windows") { return new WindowsUIFactory(); } else if (os.toLowerCase() === "mac") { return new MacUIFactory(); } else { throw new Error(`Unknown OS: ${os}`); } }}
// Step 7: Use the patternfunction createApplicationUI(factory: UIFactory): string[] { /** Create application UI using the factory */ const button = factory.createButton(); const dialog = factory.createDialog(); const menu = factory.createMenu();
return [ button.render(), dialog.render(), menu.render() ];}
// Usagefunction main(): void { // Detect OS (in real app, this would be actual OS detection) const currentOS = "windows"; // or "mac"
// Create UI factory for current OS const uiFactory = UIFactoryProvider.getFactory(currentOS);
// Create application UI - all components match the OS! const uiComponents = createApplicationUI(uiFactory);
console.log(`🖥️ Application UI (${currentOS.toUpperCase()}):`); uiComponents.forEach(component => console.log(` ${component}`));
// All components are from the same OS family - guaranteed consistency!}
main();#include <string>#include <memory>#include <vector>#include <stdexcept>#include <iostream>#include <algorithm>
// Step 1: Define abstract product interfacesclass Button {public: virtual ~Button() = default; virtual std::string render() = 0;};
class Dialog {public: virtual ~Dialog() = default; virtual std::string render() = 0;};
class Menu {public: virtual ~Menu() = default; virtual std::string render() = 0;};
// Step 2: Define abstract factoryclass UIFactory {public: virtual ~UIFactory() = default; // Abstract Factory - creates families of UI components virtual std::unique_ptr<Button> createButton() = 0; virtual std::unique_ptr<Dialog> createDialog() = 0; virtual std::unique_ptr<Menu> createMenu() = 0;};
// Step 3: Create concrete products for Windows familyclass WindowsButton : public Button {public: std::string render() override { return "🪟 Windows Button"; }};
class WindowsDialog : public Dialog {public: std::string render() override { return "🪟 Windows Dialog"; }};
class WindowsMenu : public Menu {public: std::string render() override { return "🪟 Windows Menu"; }};
// Step 4: Create concrete products for Mac familyclass MacButton : public Button {public: std::string render() override { return "🍎 Mac Button"; }};
class MacDialog : public Dialog {public: std::string render() override { return "🍎 Mac Dialog"; }};
class MacMenu : public Menu {public: std::string render() override { return "🍎 Mac Menu"; }};
// Step 5: Create concrete factoriesclass WindowsUIFactory : public UIFactory {public: // Concrete Factory - creates Windows UI component family std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
std::unique_ptr<Dialog> createDialog() override { return std::make_unique<WindowsDialog>(); }
std::unique_ptr<Menu> createMenu() override { return std::make_unique<WindowsMenu>(); }};
class MacUIFactory : public UIFactory {public: // Concrete Factory - creates Mac UI component family std::unique_ptr<Button> createButton() override { return std::make_unique<MacButton>(); }
std::unique_ptr<Dialog> createDialog() override { return std::make_unique<MacDialog>(); }
std::unique_ptr<Menu> createMenu() override { return std::make_unique<MacMenu>(); }};
// Step 6: Factory providerclass UIFactoryProvider {public: static std::unique_ptr<UIFactory> getFactory(const std::string& os) { std::string osLower = os; std::transform(osLower.begin(), osLower.end(), osLower.begin(), ::tolower);
if (osLower == "windows") { return std::make_unique<WindowsUIFactory>(); } else if (osLower == "mac") { return std::make_unique<MacUIFactory>(); } else { throw std::invalid_argument("Unknown OS: " + os); } }};
// Step 7: Use the patternstd::vector<std::string> createApplicationUI(UIFactory& factory) { // Create application UI using the factory auto button = factory.createButton(); auto dialog = factory.createDialog(); auto menu = factory.createMenu();
return { button->render(), dialog->render(), menu->render() };}
// Usageint main() { // Detect OS (in real app, this would be actual OS detection) std::string currentOS = "windows"; // or "mac"
// Create UI factory for current OS auto uiFactory = UIFactoryProvider::getFactory(currentOS);
// Create application UI - all components match the OS! auto uiComponents = createApplicationUI(*uiFactory);
std::cout << "🖥️ Application UI ("; std::transform(currentOS.begin(), currentOS.end(), currentOS.begin(), ::toupper); std::cout << currentOS << "):" << std::endl;
for (const auto& component : uiComponents) { std::cout << " " << component << std::endl; }
// All components are from the same OS family - guaranteed consistency!
return 0;}using System;using System.Collections.Generic;
// Step 1: Define abstract product interfacespublic interface IButton{ string Render();}
public interface IDialog{ string Render();}
public interface IMenu{ string Render();}
// Step 2: Define abstract factorypublic interface IUIFactory{ // Abstract Factory - creates families of UI components IButton CreateButton(); IDialog CreateDialog(); IMenu CreateMenu();}
// Step 3: Create concrete products for Windows familypublic class WindowsButton : IButton{ public string Render() { return "🪟 Windows Button"; }}
public class WindowsDialog : IDialog{ public string Render() { return "🪟 Windows Dialog"; }}
public class WindowsMenu : IMenu{ public string Render() { return "🪟 Windows Menu"; }}
// Step 4: Create concrete products for Mac familypublic class MacButton : IButton{ public string Render() { return "🍎 Mac Button"; }}
public class MacDialog : IDialog{ public string Render() { return "🍎 Mac Dialog"; }}
public class MacMenu : IMenu{ public string Render() { return "🍎 Mac Menu"; }}
// Step 5: Create concrete factoriespublic class WindowsUIFactory : IUIFactory{ // Concrete Factory - creates Windows UI component family public IButton CreateButton() { return new WindowsButton(); }
public IDialog CreateDialog() { return new WindowsDialog(); }
public IMenu CreateMenu() { return new WindowsMenu(); }}
public class MacUIFactory : IUIFactory{ // Concrete Factory - creates Mac UI component family public IButton CreateButton() { return new MacButton(); }
public IDialog CreateDialog() { return new MacDialog(); }
public IMenu CreateMenu() { return new MacMenu(); }}
// Step 6: Factory providerpublic class UIFactoryProvider{ public static IUIFactory GetFactory(string os) { if (os.ToLower() == "windows") { return new WindowsUIFactory(); } else if (os.ToLower() == "mac") { return new MacUIFactory(); } else { throw new ArgumentException($"Unknown OS: {os}"); } }}
// Step 7: Use the patternpublic class Program{ public static List<string> CreateApplicationUI(IUIFactory factory) { // Create application UI using the factory IButton button = factory.CreateButton(); IDialog dialog = factory.CreateDialog(); IMenu menu = factory.CreateMenu();
return new List<string> { button.Render(), dialog.Render(), menu.Render() }; }
static void Main() { // Detect OS (in real app, this would be actual OS detection) string currentOS = "windows"; // or "mac"
// Create UI factory for current OS IUIFactory uiFactory = UIFactoryProvider.GetFactory(currentOS);
// Create application UI - all components match the OS! List<string> uiComponents = CreateApplicationUI(uiFactory);
Console.WriteLine($"🖥️ Application UI ({currentOS.ToUpper()}):"); foreach (string component in uiComponents) { Console.WriteLine($" {component}"); }
// All components are from the same OS family - guaranteed consistency! }}package main
import ( "fmt" "strings")
// Step 1type Button interface{ Render() string }type Dialog interface{ Render() string }type Menu interface{ Render() string }
// Step 2 — Windows productstype WindowsButton struct{}
func (WindowsButton) Render() string { return "🪟 Windows Button" }
type WindowsDialog struct{}
func (WindowsDialog) Render() string { return "🪟 Windows Dialog" }
type WindowsMenu struct{}
func (WindowsMenu) Render() string { return "🪟 Windows Menu" }
// Mac productstype MacButton struct{}
func (MacButton) Render() string { return "🍎 Mac Button" }
type MacDialog struct{}
func (MacDialog) Render() string { return "🍎 Mac Dialog" }
type MacMenu struct{}
func (MacMenu) Render() string { return "🍎 Mac Menu" }
// Step 3 — abstract factorytype UIFactory interface { CreateButton() Button CreateDialog() Dialog CreateMenu() Menu}
type WindowsUIFactory struct{}
func (WindowsUIFactory) CreateButton() Button { return WindowsButton{} }func (WindowsUIFactory) CreateDialog() Dialog { return WindowsDialog{} }func (WindowsUIFactory) CreateMenu() Menu { return WindowsMenu{} }
type MacUIFactory struct{}
func (MacUIFactory) CreateButton() Button { return MacButton{} }func (MacUIFactory) CreateDialog() Dialog { return MacDialog{} }func (MacUIFactory) CreateMenu() Menu { return MacMenu{} }
// Step 4 — providertype UIFactoryProvider struct{}
func (UIFactoryProvider) GetFactory(osName string) (UIFactory, error) { switch strings.ToLower(osName) { case "windows": return WindowsUIFactory{}, nil case "mac": return MacUIFactory{}, nil default: return nil, fmt.Errorf("unknown OS: %s", osName) }}
// Step 5 — use patternfunc CreateApplicationUI(factory UIFactory) []string { b := factory.CreateButton() d := factory.CreateDialog() m := factory.CreateMenu() return []string{b.Render(), d.Render(), m.Render()}}
func main() { currentOS := "windows" p := UIFactoryProvider{} uiFactory, err := p.GetFactory(currentOS) if err != nil { panic(err) } ui := CreateApplicationUI(uiFactory) fmt.Printf("🖥️ Application UI (%s):\n", strings.ToUpper(currentOS)) for _, component := range ui { fmt.Printf(" %s\n", component) }}// The Solution: Abstract Factory Patterntrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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(); }}interface ProductA { // Product interface}
interface ProductB { // Product interface}
interface AbstractFactory { createProductA(): ProductA; createProductB(): ProductB;}
class ConcreteFactory1 implements AbstractFactory { createProductA(): ProductA { return new ProductA1(); }
createProductB(): ProductB { return new ProductB1(); }}class ProductA {public: virtual ~ProductA() = default;};
class ProductB {public: virtual ~ProductB() = default;};
class AbstractFactory {public: virtual ~AbstractFactory() = default; virtual std::unique_ptr<ProductA> createProductA() = 0; virtual std::unique_ptr<ProductB> createProductB() = 0;};
class ConcreteFactory1 : public AbstractFactory {public: std::unique_ptr<ProductA> createProductA() override { return std::make_unique<ProductA1>(); }
std::unique_ptr<ProductB> createProductB() override { return std::make_unique<ProductB1>(); }};public interface IProductA{ // Product interface}
public interface IProductB{ // Product interface}
public interface IAbstractFactory{ IProductA CreateProductA(); IProductB CreateProductB();}
public class ConcreteFactory1 : IAbstractFactory{ public IProductA CreateProductA() { return new ProductA1(); }
public IProductB CreateProductB() { return new ProductB1(); }}package main
type IProductA interface{}type IProductB interface{}
type IAbstractFactory interface { CreateProductA() IProductA CreateProductB() IProductB}
type ProductA1 struct{}type ProductB1 struct{}
type ConcreteFactory1 struct{}
func (ConcreteFactory1) CreateProductA() IProductA { return ProductA1{} }func (ConcreteFactory1) CreateProductB() IProductB { return ProductB1{} }// 1. Simple Abstract Factorytrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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"); } }}class FactoryProvider { static getFactory(type: string): AbstractFactory { if (type === "type1") { return new ConcreteFactory1(); } else if (type === "type2") { return new ConcreteFactory2(); } else { throw new Error("Unknown type"); } }}class FactoryProvider {public: static std::unique_ptr<AbstractFactory> getFactory(const std::string& type) { if (type == "type1") { return std::make_unique<ConcreteFactory1>(); } else if (type == "type2") { return std::make_unique<ConcreteFactory2>(); } else { throw std::invalid_argument("Unknown type"); } }};public class FactoryProvider{ public static IAbstractFactory GetFactory(string type) { if (type == "type1") { return new ConcreteFactory1(); } else if (type == "type2") { return new ConcreteFactory2(); } else { throw new ArgumentException("Unknown type"); } }}package main
import "fmt"
type FactoryProvider struct{}
func (FactoryProvider) GetFactory(typ string) (IAbstractFactory, error) { switch typ { case "type1": return ConcreteFactory1{}, nil case "type2": return ConcreteFactory2{}, nil default: return nil, fmt.Errorf("unknown type") }}
// ConcreteFactory2 is a placeholder — implement like ConcreteFactory1 for family 2.type ConcreteFactory2 struct{}
func (ConcreteFactory2) CreateProductA() IProductA { return ProductA1{} }func (ConcreteFactory2) CreateProductB() IProductB { return ProductB1{} }// 2. Factory Provider Patterntrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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!);// ❌ Bad: Mixing products from different familiesconst factory1 = new ItalianFactory();const factory2 = new AmericanFactory();
const meal = [ factory1.createAppetizer(), // Italian factory2.createMain() // American - incompatible!];
// ✅ Good: Use same factory for all productsconst factory = new ItalianFactory();const goodMeal = [ factory.createAppetizer(), // Italian factory.createMain() // Italian - compatible!];// ❌ Bad: Mixing products from different familiesauto factory1 = std::make_unique<WindowsUIFactory>();auto factory2 = std::make_unique<MacUIFactory>();
std::vector<std::unique_ptr<Button>> ui;ui.push_back(factory1->createButton()); // Windowsui.push_back(factory2->createButton()); // Mac - incompatible!
// ✅ Good: Use same factory for all productsauto factory = std::make_unique<WindowsUIFactory>();std::vector<std::unique_ptr<Button>> goodUI;goodUI.push_back(factory->createButton()); // WindowsgoodUI.push_back(factory->createButton()); // Windows - compatible!// ❌ Bad: Mixing products from different familiesIUIFactory factory1 = new WindowsUIFactory();IUIFactory factory2 = new MacUIFactory();
List<object> ui = new List<object>{ factory1.CreateButton(), // Windows factory2.CreateDialog() // Mac - incompatible!};
// ✅ Good: Use same factory for all productsIUIFactory factory = new WindowsUIFactory();List<object> goodUI = new List<object>{ factory.CreateButton(), // Windows factory.CreateDialog() // Windows - compatible!};package main
// Same intent as C#/Java: factories keep a product *family* compatible.
type IButton anytype IDialog any
type IUIFactory interface { CreateButton() IButton CreateDialog() IDialog}
type WindowsUIFactory struct{}
func (WindowsUIFactory) CreateButton() IButton { return struct{}{} }func (WindowsUIFactory) CreateDialog() IDialog { return struct{}{} }
type MacUIFactory struct{}
func (MacUIFactory) CreateButton() IButton { return struct{}{} }func (MacUIFactory) CreateDialog() IDialog { return struct{}{} }
func main() { // ❌ Bad: Mixing products from different families var f1 IUIFactory = WindowsUIFactory{} var f2 IUIFactory = MacUIFactory{} ui := []any{f1.CreateButton(), f2.CreateDialog()} // incompatible family mix _ = ui
// ✅ Good: same factory for whole UI factory := IUIFactory(WindowsUIFactory{}) good := []any{factory.CreateButton(), factory.CreateDialog()} _ = good}// Mistake 1: Mixing Products from Different Familiesstruct App;impl App { fn render_windows_ui(&self) { println!("Windows button"); println!("Windows checkbox"); } fn render_mac_ui(&self) { println!("Mac button"); println!("Mac checkbox"); }}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!// ❌ Bad: Creating objects individually - can mix incompatible onesconst button = new WindowsButton();const dialog = new MacDialog(); // Mixed! ❌
// ✅ Good: Use Abstract Factory to ensure compatibilityconst factory = UIFactoryProvider.getFactory("windows");const goodButton = factory.createButton();const goodDialog = factory.createDialog(); // Both Windows - compatible!// ❌ Bad: Creating objects individually - can mix incompatible onesauto button = std::make_unique<WindowsButton>();auto dialog = std::make_unique<MacDialog>(); // Mixed! ❌
// ✅ Good: Use Abstract Factory to ensure compatibilityauto factory = UIFactoryProvider::getFactory("windows");auto goodButton = factory->createButton();auto goodDialog = factory->createDialog(); // Both Windows - compatible!// ❌ Bad: Creating objects individually - can mix incompatible onesIButton button = new WindowsButton();IDialog dialog = new MacDialog(); // Mixed! ❌
// ✅ Good: Use Abstract Factory to ensure compatibilityIUIFactory factory = UIFactoryProvider.GetFactory("windows");IButton goodButton = factory.CreateButton();IDialog goodDialog = factory.CreateDialog(); // Both Windows - compatible!package main
// Stubs — mirror the “don’t mix themes; use factory” story from sibling tabs.
type IButton interface{}type IDialog interface{}
type WindowsButton struct{}type MacDialog struct{}
type IUIFactory interface { CreateButton() IButton CreateDialog() IDialog}
type windowsFactory struct{}
func (windowsFactory) CreateButton() IButton { return WindowsButton{} }func (windowsFactory) CreateDialog() IDialog { return windowsDialog{} }
type windowsDialog struct{}
func UIFactoryProviderGetFactory(theme string) IUIFactory { if theme == "windows" { return windowsFactory{} } return nil}
func main() { // ❌ Bad: mix concrete products from different families var _ IButton = WindowsButton{} var _ IDialog = MacDialog{} // Mixed! ❌
// ✅ Good: one factory → compatible products f := UIFactoryProviderGetFactory("windows") _ = f.CreateButton() _ = f.CreateDialog() // both Windows — compatible}// Mistake 2: Not Using Abstract Factory When Neededstruct App;impl App { fn render_windows_ui(&self) { println!("Windows button"); println!("Windows checkbox"); } fn render_mac_ui(&self) { println!("Mac button"); println!("Mac checkbox"); }}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 }}// ❌ Bad: Using Abstract Factory for independent objectsinterface SimpleFactory { createA(): A; createB(): B; // B doesn't need to match A!}
// ✅ Better: Use simple Factory Patternclass SimpleFactory { createA(): A { return new A(); }
createB(): B { return new B(); // Independent creation }}// ❌ Bad: Using Abstract Factory for independent objectsclass SimpleFactory {public: virtual ~SimpleFactory() = default; virtual std::unique_ptr<A> createA() = 0; virtual std::unique_ptr<B> createB() = 0; // B doesn't need to match A!};
// ✅ Better: Use simple Factory Patternclass SimpleFactory {public: std::unique_ptr<A> createA() { return std::make_unique<A>(); }
std::unique_ptr<B> createB() { return std::make_unique<B>(); // Independent creation }};// ❌ Bad: Using Abstract Factory for independent objectspublic interface ISimpleFactory{ A CreateA(); B CreateB(); // B doesn't need to match A!}
// ✅ Better: Use simple Factory Patternpublic class SimpleFactory{ public A CreateA() { return new A(); }
public B CreateB() { return new B(); // Independent creation }}package main
// ❌ Bad: forcing one family interface when A and B are unrelated products// (same narrative as sibling tabs — no runnable “bad” block needed).
// ✅ Better: a simple helper type with independent creators.
type A struct{}type B struct{}
type SimpleFactory struct{}
func (SimpleFactory) CreateA() A { return A{} }func (SimpleFactory) CreateB() B { return B{} } // Independent creation// Mistake 3: Over-Engineering Simple Casesstruct App;impl App { fn render_windows_ui(&self) { println!("Windows button"); println!("Windows checkbox"); } fn render_mac_ui(&self) { println!("Mac button"); println!("Mac checkbox"); }}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”from abc import ABC, abstractmethod
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!interface UIFactory { Button createButton(); Dialog createDialog();}
class WindowsFactory implements UIFactory { public Button createButton() { return new WindowsButton(); } public Dialog createDialog() { return new WindowsDialog(); }}
// UsageUIFactory factory = new WindowsFactory();Button button = factory.createButton(); // WindowsDialog dialog = factory.createDialog(); // Windows - compatible!interface UIFactory { createButton(): Button; createDialog(): Dialog;}
class WindowsFactory implements UIFactory { createButton(): Button { return new WindowsButton(); } createDialog(): Dialog { return new WindowsDialog(); }}
// Usageconst factory = new WindowsFactory();const button = factory.createButton(); // Windowsconst dialog = factory.createDialog(); // Windows - compatible!class UIFactory {public: virtual Button* createButton() = 0; virtual Dialog* createDialog() = 0;};
class WindowsFactory : public UIFactory {public: Button* createButton() override { return new WindowsButton(); } Dialog* createDialog() override { return new WindowsDialog(); }};
// UsageUIFactory* factory = new WindowsFactory();Button* button = factory->createButton(); // WindowsDialog* dialog = factory->createDialog(); // Windows - compatible!interface IUIFactory { IButton CreateButton(); IDialog CreateDialog();}
class WindowsFactory : IUIFactory { public IButton CreateButton() => new WindowsButton(); public IDialog CreateDialog() => new WindowsDialog();}
// UsageIUIFactory factory = new WindowsFactory();var button = factory.CreateButton(); // Windowsvar dialog = factory.CreateDialog(); // Windows - compatible!package main
type IButton interface{}type IDialog interface{}
type IUIFactory interface { CreateButton() IButton CreateDialog() IDialog}
type WindowsButton struct{}type WindowsDialog struct{}
type WindowsFactory struct{}
func (WindowsFactory) CreateButton() IButton { return WindowsButton{} }func (WindowsFactory) CreateDialog() IDialog { return WindowsDialog{} }
func main() { factory := IUIFactory(&WindowsFactory{}) button := factory.CreateButton() // Windows dialog := factory.CreateDialog() // Windows — compatible family _, _ = button, dialog}// Simple Exampletrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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”from abc import ABC, abstractmethod
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!interface AbstractFactory { A createA(); B createB();}
class Factory1 implements AbstractFactory { public A createA() { return new A1(); } public B createB() { return new B1(); }}
// UsageAbstractFactory factory = new Factory1();A a = factory.createA(); // A1B b = factory.createB(); // B1 - compatible!interface AbstractFactory { createA(): A; createB(): B;}
class Factory1 implements AbstractFactory { createA(): A { return new A1(); } createB(): B { return new B1(); }}
// Usageconst factory = new Factory1();const a = factory.createA(); // A1const b = factory.createB(); // B1 - compatible!class AbstractFactory {public: virtual A* createA() = 0; virtual B* createB() = 0;};
class Factory1 : public AbstractFactory {public: A* createA() override { return new A1(); } B* createB() override { return new B1(); }};
// UsageAbstractFactory* factory = new Factory1();A* a = factory->createA(); // A1B* b = factory->createB(); // B1 - compatible!interface IAbstractFactory { IProductA CreateA(); IProductB CreateB();}
class Factory1 : IAbstractFactory { public IProductA CreateA() => new A1(); public IProductB CreateB() => new B1();}
// UsageIAbstractFactory factory = new Factory1();var a = factory.CreateA(); // A1var b = factory.CreateB(); // B1 - compatible!package main
type IProductA interface{}type IProductB interface{}
type A1 struct{}type B1 struct{}
type IAbstractFactory interface { CreateA() IProductA CreateB() IProductB}
type Factory1 struct{}
func (Factory1) CreateA() IProductA { return A1{} }func (Factory1) CreateB() IProductB { return B1{} }
func main() { factory := IAbstractFactory(Factory1{}) a := factory.CreateA() // A1 b := factory.CreateB() // B1 — compatible family _, _ = a, b}// Common Pattern Structuretrait Button { fn render(&self);}trait Checkbox { fn render(&self);}trait UiFactory { fn button(&self) -> Box<dyn Button>; fn checkbox(&self) -> Box<dyn Checkbox>;}struct WindowsFactory;struct WindowsButton;struct WindowsCheckbox;impl Button for WindowsButton { fn render(&self) { println!("Windows button"); }}impl Checkbox for WindowsCheckbox { fn render(&self) { println!("Windows checkbox"); }}impl UiFactory for WindowsFactory { fn button(&self) -> Box<dyn Button> { Box::new(WindowsButton) } fn checkbox(&self) -> Box<dyn Checkbox> { Box::new(WindowsCheckbox) }}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! 🏭