+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 283 of 354

📘 Factory Pattern: Object Creation

Master factory pattern: object creation in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻

What you'll learn

  • Understand the concept fundamentals 🎯
  • Apply the concept in real projects 🏗️
  • Debug common issues 🐛
  • Write type-safe code ✨

🎯 Introduction

Have you ever played with LEGO blocks? 🧱 You know how there’s a specific way to create each type of piece, and once you have the right pieces, you can build amazing things? That’s exactly what the Factory Pattern is all about in programming! 🏗️

Today, we’re diving into one of the most useful design patterns in TypeScript - the Factory Pattern. It’s like having a magical machine that creates objects for you based on what you need. No more repetitive code, no more confusion about which object to create - just clean, organized, and powerful object creation! 🎨

By the end of this tutorial, you’ll be creating objects like a pro, understanding when to use factories, and building more maintainable TypeScript applications. Let’s turn you into a master object creator! 💪

📚 Understanding the Factory Pattern

Think of a factory pattern like a real-world factory. Let’s say you’re at a pizza restaurant 🍕. You don’t go into the kitchen and make your own pizza - you just tell them what type you want (Margherita, Pepperoni, Hawaiian), and the kitchen (our factory) knows exactly how to make it!

In programming terms, the Factory Pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. It’s like having a smart assistant that knows how to create different types of objects based on what you ask for. 🤖

Here’s why factories are awesome:

  • Encapsulation: All the complex creation logic is hidden away 📦
  • Flexibility: Easy to add new types without changing existing code 🔄
  • Consistency: Objects are created the same way every time 🎯
  • Testing: Much easier to test when creation is centralized 🧪

🔧 Basic Syntax and Usage

Let’s start with a simple factory pattern. We’ll create different types of vehicles! 🚗

// First, let's define our vehicle types 🚙
interface Vehicle {
  type: string;
  wheels: number;
  start(): void;
  stop(): void;
}

// Now, let's create specific vehicle classes 🏎️
class Car implements Vehicle {
  type = "Car";
  wheels = 4;
  
  start(): void {
    console.log("🚗 Vroom! Car is starting...");
  }
  
  stop(): void {
    console.log("🛑 Car has stopped.");
  }
}

class Motorcycle implements Vehicle {
  type = "Motorcycle";
  wheels = 2;
  
  start(): void {
    console.log("🏍️ Vroooom! Motorcycle is revving up!");
  }
  
  stop(): void {
    console.log("🛑 Motorcycle has stopped.");
  }
}

class Bicycle implements Vehicle {
  type = "Bicycle";
  wheels = 2;
  
  start(): void {
    console.log("🚴 Let's pedal! Bicycle is ready to go!");
  }
  
  stop(): void {
    console.log("🛑 Bicycle has stopped.");
  }
}

// Here's our magical factory! 🏭
class VehicleFactory {
  static createVehicle(type: string): Vehicle {
    switch (type.toLowerCase()) {
      case 'car':
        return new Car();
      case 'motorcycle':
        return new Motorcycle();
      case 'bicycle':
        return new Bicycle();
      default:
        throw new Error(`Unknown vehicle type: ${type} 🤷`);
    }
  }
}

// Let's use our factory! 🎉
const myCar = VehicleFactory.createVehicle('car');
myCar.start(); // 🚗 Vroom! Car is starting...

const myBike = VehicleFactory.createVehicle('bicycle');
myBike.start(); // 🚴 Let's pedal! Bicycle is ready to go!

See how clean that is? Instead of remembering how to create each vehicle type, we just ask the factory! 🌟

💡 Practical Examples

Example 1: Game Character Factory 🎮

Let’s create a factory for different game characters with unique abilities!

// Define our character interface 🦸
interface GameCharacter {
  name: string;
  health: number;
  attackPower: number;
  specialAbility: string;
  attack(): void;
  useSpecialAbility(): void;
}

// Create different character types 🗡️
class Warrior implements GameCharacter {
  name = "Warrior";
  health = 150;
  attackPower = 20;
  specialAbility = "Berserker Rage";
  
  attack(): void {
    console.log(`⚔️ ${this.name} swings their mighty sword for ${this.attackPower} damage!`);
  }
  
  useSpecialAbility(): void {
    console.log(`🔥 ${this.name} enters ${this.specialAbility}! Attack power doubled!`);
    this.attackPower *= 2;
  }
}

class Wizard implements GameCharacter {
  name = "Wizard";
  health = 80;
  attackPower = 35;
  specialAbility = "Meteor Storm";
  
  attack(): void {
    console.log(`🪄 ${this.name} casts a spell for ${this.attackPower} damage!`);
  }
  
  useSpecialAbility(): void {
    console.log(`☄️ ${this.name} summons ${this.specialAbility}! Massive area damage!`);
  }
}

class Archer implements GameCharacter {
  name = "Archer";
  health = 100;
  attackPower = 25;
  specialAbility = "Rain of Arrows";
  
  attack(): void {
    console.log(`🏹 ${this.name} shoots an arrow for ${this.attackPower} damage!`);
  }
  
  useSpecialAbility(): void {
    console.log(`🎯 ${this.name} unleashes ${this.specialAbility}! Arrows everywhere!`);
  }
}

// Our character factory with difficulty levels! 🏭
class CharacterFactory {
  static createCharacter(type: string, difficulty: 'easy' | 'normal' | 'hard' = 'normal'): GameCharacter {
    let character: GameCharacter;
    
    switch (type.toLowerCase()) {
      case 'warrior':
        character = new Warrior();
        break;
      case 'wizard':
        character = new Wizard();
        break;
      case 'archer':
        character = new Archer();
        break;
      default:
        throw new Error(`Unknown character type: ${type} 🤔`);
    }
    
    // Adjust stats based on difficulty 🎯
    switch (difficulty) {
      case 'easy':
        character.health *= 1.5;
        character.attackPower *= 1.2;
        console.log(`🟢 Easy mode: ${character.name} has boosted stats!`);
        break;
      case 'hard':
        character.health *= 0.8;
        character.attackPower *= 0.9;
        console.log(`🔴 Hard mode: ${character.name} has reduced stats!`);
        break;
    }
    
    return character;
  }
}

// Let's create some characters! 🎮
const easyWarrior = CharacterFactory.createCharacter('warrior', 'easy');
easyWarrior.attack(); // ⚔️ Warrior swings their mighty sword for 24 damage!

const hardWizard = CharacterFactory.createCharacter('wizard', 'hard');
hardWizard.useSpecialAbility(); // ☄️ Wizard summons Meteor Storm! Massive area damage!

Example 2: Notification Factory 📱

Let’s build a factory for creating different types of notifications!

// Notification interface 📬
interface Notification {
  recipient: string;
  message: string;
  send(): Promise<boolean>;
  format(): string;
}

// Email notification 📧
class EmailNotification implements Notification {
  constructor(public recipient: string, public message: string) {}
  
  format(): string {
    return `📧 Email to: ${this.recipient}\n` +
           `Subject: Important Notification\n` +
           `Body: ${this.message}`;
  }
  
  async send(): Promise<boolean> {
    console.log(this.format());
    console.log("✅ Email sent successfully!");
    return true;
  }
}

// SMS notification 📱
class SMSNotification implements Notification {
  constructor(public recipient: string, public message: string) {}
  
  format(): string {
    // SMS has character limit, let's truncate if needed 📏
    const truncated = this.message.length > 160 
      ? this.message.substring(0, 157) + "..." 
      : this.message;
    return `📱 SMS to: ${this.recipient}\nMessage: ${truncated}`;
  }
  
  async send(): Promise<boolean> {
    console.log(this.format());
    console.log("✅ SMS sent successfully!");
    return true;
  }
}

// Push notification 🔔
class PushNotification implements Notification {
  constructor(public recipient: string, public message: string) {}
  
  format(): string {
    return `🔔 Push Notification\nUser: ${this.recipient}\n${this.message}`;
  }
  
  async send(): Promise<boolean> {
    console.log(this.format());
    console.log("✅ Push notification delivered!");
    return true;
  }
}

// Advanced factory with configuration 🏭
class NotificationFactory {
  private static emailDomain = "@example.com";
  
  static createNotification(
    type: 'email' | 'sms' | 'push',
    recipient: string,
    message: string
  ): Notification {
    // Add validation 🛡️
    if (!recipient || !message) {
      throw new Error("Recipient and message are required! 🚫");
    }
    
    switch (type) {
      case 'email':
        // Add domain if not present 📧
        const emailRecipient = recipient.includes('@') 
          ? recipient 
          : `${recipient}${this.emailDomain}`;
        return new EmailNotification(emailRecipient, message);
        
      case 'sms':
        // Clean phone number 📱
        const phoneNumber = recipient.replace(/\D/g, '');
        return new SMSNotification(phoneNumber, message);
        
      case 'push':
        return new PushNotification(recipient, message);
        
      default:
        throw new Error(`Unknown notification type: ${type} 🤷`);
    }
  }
  
  // Bulk notification creator! 📢
  static createBulkNotifications(
    type: 'email' | 'sms' | 'push',
    recipients: string[],
    message: string
  ): Notification[] {
    return recipients.map(recipient => 
      this.createNotification(type, recipient, message)
    );
  }
}

// Let's send some notifications! 📬
const email = NotificationFactory.createNotification(
  'email', 
  'john', 
  'Your order has been shipped! 📦'
);
email.send();

const bulkSMS = NotificationFactory.createBulkNotifications(
  'sms',
  ['555-1234', '555-5678'],
  'Flash sale! 50% off everything! 🎉'
);
bulkSMS.forEach(sms => sms.send());

🚀 Advanced Concepts

Abstract Factory Pattern 🏗️

Let’s level up with the Abstract Factory pattern - a factory of factories!

// Theme interfaces 🎨
interface Button {
  render(): void;
  onClick(handler: () => void): void;
}

interface TextInput {
  render(): void;
  getValue(): string;
}

// Light theme components ☀️
class LightButton implements Button {
  render(): void {
    console.log("☀️ Rendering light theme button with white background");
  }
  
  onClick(handler: () => void): void {
    console.log("🖱️ Light button clicked!");
    handler();
  }
}

class LightTextInput implements TextInput {
  private value = "";
  
  render(): void {
    console.log("☀️ Rendering light theme input with bright colors");
  }
  
  getValue(): string {
    return this.value;
  }
}

// Dark theme components 🌙
class DarkButton implements Button {
  render(): void {
    console.log("🌙 Rendering dark theme button with dark background");
  }
  
  onClick(handler: () => void): void {
    console.log("🖱️ Dark button clicked!");
    handler();
  }
}

class DarkTextInput implements TextInput {
  private value = "";
  
  render(): void {
    console.log("🌙 Rendering dark theme input with dark colors");
  }
  
  getValue(): string {
    return this.value;
  }
}

// Abstract factory interface 🏭
interface UIFactory {
  createButton(): Button;
  createTextInput(): TextInput;
}

// Concrete factories 🏗️
class LightThemeFactory implements UIFactory {
  createButton(): Button {
    return new LightButton();
  }
  
  createTextInput(): TextInput {
    return new LightTextInput();
  }
}

class DarkThemeFactory implements UIFactory {
  createButton(): Button {
    return new DarkButton();
  }
  
  createTextInput(): TextInput {
    return new DarkTextInput();
  }
}

// Application that uses the factory 📱
class Application {
  private button: Button;
  private input: TextInput;
  
  constructor(factory: UIFactory) {
    this.button = factory.createButton();
    this.input = factory.createTextInput();
  }
  
  render(): void {
    console.log("🎨 Rendering application UI...");
    this.button.render();
    this.input.render();
  }
}

// Factory with registry pattern 📚
class ThemeFactoryRegistry {
  private static factories = new Map<string, UIFactory>();
  
  static {
    // Register default themes 🎨
    this.register('light', new LightThemeFactory());
    this.register('dark', new DarkThemeFactory());
  }
  
  static register(name: string, factory: UIFactory): void {
    this.factories.set(name, factory);
    console.log(`✅ Registered ${name} theme factory`);
  }
  
  static getFactory(name: string): UIFactory {
    const factory = this.factories.get(name);
    if (!factory) {
      throw new Error(`Theme factory '${name}' not found! 🔍`);
    }
    return factory;
  }
}

// Use it! 🚀
const darkApp = new Application(ThemeFactoryRegistry.getFactory('dark'));
darkApp.render();

⚠️ Common Pitfalls and Solutions

Pitfall 1: Tight Coupling ❌

// ❌ BAD: Factory knows too much about concrete classes
class BadVehicleFactory {
  static createVehicle(type: string): Vehicle {
    if (type === 'car') {
      const car = new Car();
      car.brand = 'Toyota'; // Factory shouldn't set specific properties!
      car.color = 'Red';    // This creates tight coupling
      return car;
    }
    // ...
  }
}

// ✅ GOOD: Factory only creates, doesn't configure
class GoodVehicleFactory {
  static createVehicle(type: string, config?: VehicleConfig): Vehicle {
    const vehicle = this.getVehicleInstance(type);
    
    // Let the caller configure if needed
    if (config) {
      Object.assign(vehicle, config);
    }
    
    return vehicle;
  }
  
  private static getVehicleInstance(type: string): Vehicle {
    switch (type) {
      case 'car': return new Car();
      case 'motorcycle': return new Motorcycle();
      default: throw new Error(`Unknown type: ${type}`);
    }
  }
}

Pitfall 2: God Factory ❌

// ❌ BAD: One factory trying to do everything
class GodFactory {
  static create(type: string): any { // 😱 Using 'any'!
    if (type.includes('vehicle')) {
      // Vehicle creation logic
    } else if (type.includes('character')) {
      // Character creation logic
    } else if (type.includes('notification')) {
      // Notification creation logic
    }
    // This becomes unmaintainable! 🤯
  }
}

// ✅ GOOD: Separate factories for separate concerns
class VehicleFactory {
  static create(type: string): Vehicle {
    // Only vehicle logic
  }
}

class CharacterFactory {
  static create(type: string): GameCharacter {
    // Only character logic
  }
}

// Or use a factory provider pattern 🎯
class FactoryProvider {
  private static factories = new Map<string, any>();
  
  static registerFactory(domain: string, factory: any): void {
    this.factories.set(domain, factory);
  }
  
  static getFactory(domain: string): any {
    return this.factories.get(domain);
  }
}

Pitfall 3: Missing Type Safety ❌

// ❌ BAD: Losing type safety with strings
class UnsafeFactory {
  static create(type: string): any {
    // String-based type checking is error-prone
    if (type === 'warriro') { // Typo! 😱
      return new Warrior();
    }
  }
}

// ✅ GOOD: Use enums or literal types
enum CharacterType {
  Warrior = 'warrior',
  Wizard = 'wizard',
  Archer = 'archer'
}

class SafeCharacterFactory {
  static create(type: CharacterType): GameCharacter {
    switch (type) {
      case CharacterType.Warrior:
        return new Warrior();
      case CharacterType.Wizard:
        return new Wizard();
      case CharacterType.Archer:
        return new Archer();
      // TypeScript ensures all cases are handled! 🛡️
    }
  }
}

// Even better with a registry pattern 🌟
class TypeSafeFactory<T> {
  private creators = new Map<string, () => T>();
  
  register(type: string, creator: () => T): void {
    this.creators.set(type, creator);
  }
  
  create(type: string): T {
    const creator = this.creators.get(type);
    if (!creator) {
      throw new Error(`Type '${type}' not registered`);
    }
    return creator();
  }
}

🛠️ Best Practices

1. Use Factory Methods for Complex Creation 🏗️

class User {
  private constructor(
    private id: string,
    private name: string,
    private email: string,
    private role: string
  ) {}
  
  // Factory methods for different creation scenarios 🎯
  static createAdmin(name: string, email: string): User {
    return new User(
      crypto.randomUUID(),
      name,
      email,
      'admin'
    );
  }
  
  static createGuest(): User {
    return new User(
      crypto.randomUUID(),
      'Guest',
      '[email protected]',
      'guest'
    );
  }
  
  static fromJSON(json: string): User {
    const data = JSON.parse(json);
    return new User(data.id, data.name, data.email, data.role);
  }
}

2. Make Factories Testable 🧪

interface DIContainer {
  get<T>(token: string): T;
}

class TestableFactory {
  constructor(private container: DIContainer) {}
  
  create(type: string): Vehicle {
    // Use dependency injection for testability
    const VehicleClass = this.container.get<typeof Vehicle>(`vehicle.${type}`);
    return new VehicleClass();
  }
}

3. Use Generics for Type Safety 🛡️

interface Factory<T> {
  create(...args: any[]): T;
}

class GenericFactory<T> implements Factory<T> {
  constructor(private ctor: new (...args: any[]) => T) {}
  
  create(...args: any[]): T {
    return new this.ctor(...args);
  }
}

// Type-safe usage 🎯
const carFactory = new GenericFactory(Car);
const myCar = carFactory.create(); // Type is Car!

4. Document Factory Behavior 📚

/**
 * Creates game characters based on type and configuration
 * @throws {Error} When character type is not supported
 */
class DocumentedFactory {
  private static supportedTypes = ['warrior', 'wizard', 'archer'];
  
  static getSupportedTypes(): readonly string[] {
    return this.supportedTypes;
  }
  
  static create(type: string): GameCharacter {
    if (!this.supportedTypes.includes(type)) {
      throw new Error(
        `Unsupported type: ${type}. ` +
        `Supported types: ${this.supportedTypes.join(', ')}`
      );
    }
    // Creation logic...
  }
}

5. Consider Async Factories 🔄

class AsyncFactory {
  static async createWithData(type: string): Promise<Vehicle> {
    // Load configuration from API or file 📡
    const config = await this.loadConfig(type);
    
    const vehicle = this.createBase(type);
    Object.assign(vehicle, config);
    
    // Maybe initialize async resources 🚀
    await vehicle.initialize?.();
    
    return vehicle;
  }
  
  private static async loadConfig(type: string): Promise<any> {
    // Simulate async config loading
    return { /* config data */ };
  }
}

🧪 Hands-On Exercise

Time to put your skills to the test! Create a Pizza Factory that can make different types of pizzas with customizable toppings! 🍕

Challenge: Build a pizza factory that:

  1. Creates different pizza types (Margherita, Pepperoni, Veggie)
  2. Allows custom toppings
  3. Calculates prices based on size and toppings
  4. Has a “Pizza of the Day” feature

Try it yourself first! Here’s a starter:

interface Pizza {
  name: string;
  size: 'small' | 'medium' | 'large';
  toppings: string[];
  getPrice(): number;
  describe(): string;
}

// Your implementation here! 🚀
🎯 Click here for the solution
// Pizza interface 🍕
interface Pizza {
  name: string;
  size: 'small' | 'medium' | 'large';
  toppings: string[];
  getPrice(): number;
  describe(): string;
}

// Base pizza class 🍕
abstract class BasePizza implements Pizza {
  constructor(
    public name: string,
    public size: 'small' | 'medium' | 'large',
    public toppings: string[]
  ) {}
  
  getPrice(): number {
    const basePrice = this.getBasePrice();
    const sizeMultiplier = {
      small: 0.8,
      medium: 1.0,
      large: 1.3
    }[this.size];
    
    const toppingsPrice = this.toppings.length * 1.5;
    
    return Number((basePrice * sizeMultiplier + toppingsPrice).toFixed(2));
  }
  
  describe(): string {
    return `🍕 ${this.name} (${this.size})\n` +
           `   Toppings: ${this.toppings.join(', ') || 'None'}\n` +
           `   Price: $${this.getPrice()}`;
  }
  
  abstract getBasePrice(): number;
}

// Specific pizza types 🍕
class MargheritaPizza extends BasePizza {
  constructor(size: 'small' | 'medium' | 'large') {
    super('Margherita', size, ['mozzarella', 'tomato', 'basil']);
  }
  
  getBasePrice(): number {
    return 10;
  }
}

class PepperoniPizza extends BasePizza {
  constructor(size: 'small' | 'medium' | 'large') {
    super('Pepperoni', size, ['mozzarella', 'pepperoni', 'tomato sauce']);
  }
  
  getBasePrice(): number {
    return 12;
  }
}

class VeggiePizza extends BasePizza {
  constructor(size: 'small' | 'medium' | 'large') {
    super('Veggie', size, ['mozzarella', 'bell peppers', 'mushrooms', 'onions', 'olives']);
  }
  
  getBasePrice(): number {
    return 11;
  }
}

class CustomPizza extends BasePizza {
  constructor(size: 'small' | 'medium' | 'large', toppings: string[]) {
    super('Custom', size, toppings);
  }
  
  getBasePrice(): number {
    return 9; // Base price for custom pizza
  }
}

// Pizza factory with all the features! 🏭
class PizzaFactory {
  private static pizzaOfTheDay: { type: string; discount: number } = {
    type: 'margherita',
    discount: 0.2
  };
  
  static createPizza(
    type: string,
    size: 'small' | 'medium' | 'large' = 'medium',
    extraToppings: string[] = []
  ): Pizza {
    let pizza: Pizza;
    
    switch (type.toLowerCase()) {
      case 'margherita':
        pizza = new MargheritaPizza(size);
        break;
      case 'pepperoni':
        pizza = new PepperoniPizza(size);
        break;
      case 'veggie':
        pizza = new VeggiePizza(size);
        break;
      case 'custom':
        pizza = new CustomPizza(size, extraToppings);
        break;
      default:
        throw new Error(`Unknown pizza type: ${type} 🤷`);
    }
    
    // Add extra toppings if provided 🌶️
    if (extraToppings.length > 0 && type !== 'custom') {
      pizza.toppings.push(...extraToppings);
    }
    
    // Apply pizza of the day discount 🎉
    if (type.toLowerCase() === this.pizzaOfTheDay.type) {
      const originalPrice = pizza.getPrice();
      const discountedPrice = originalPrice * (1 - this.pizzaOfTheDay.discount);
      
      console.log(`🎊 Pizza of the Day! ${this.pizzaOfTheDay.discount * 100}% off!`);
      console.log(`💰 Original: $${originalPrice} → Discounted: $${discountedPrice.toFixed(2)}`);
    }
    
    return pizza;
  }
  
  static setPizzaOfTheDay(type: string, discount: number): void {
    this.pizzaOfTheDay = { type: type.toLowerCase(), discount };
    console.log(`📢 New Pizza of the Day: ${type} with ${discount * 100}% off!`);
  }
  
  static createPizzaCombo(pizzas: Array<{type: string, size: 'small' | 'medium' | 'large'}>): Pizza[] {
    return pizzas.map(p => this.createPizza(p.type, p.size));
  }
}

// Let's make some pizzas! 🍕
console.log("🍕 Welcome to TypeScript Pizza Factory! 🍕\n");

const margherita = PizzaFactory.createPizza('margherita', 'large');
console.log(margherita.describe());

const customPizza = PizzaFactory.createPizza('custom', 'medium', ['ham', 'pineapple', 'extra cheese']);
console.log(customPizza.describe());

// Set pizza of the day
PizzaFactory.setPizzaOfTheDay('veggie', 0.25);
const veggieDeal = PizzaFactory.createPizza('veggie', 'large');

// Create a combo
const familyCombo = PizzaFactory.createPizzaCombo([
  { type: 'margherita', size: 'large' },
  { type: 'pepperoni', size: 'large' },
  { type: 'veggie', size: 'medium' }
]);

console.log("\n🎉 Family Combo Order:");
familyCombo.forEach(pizza => console.log(pizza.describe()));

🎓 Key Takeaways

Wow, you’ve mastered the Factory Pattern! 🎉 Here’s what you’ve learned:

  1. Factory Pattern Basics 🏭 - Creating objects through a centralized interface
  2. Simple Factory 🔧 - Basic implementation with switch statements
  3. Abstract Factory 🏗️ - Creating families of related objects
  4. Type Safety 🛡️ - Using TypeScript features for safer factories
  5. Best Practices 🌟 - Making factories testable, maintainable, and flexible

The Factory Pattern is like having a skilled craftsperson who knows exactly how to create what you need. It keeps your code organized, makes it easy to add new types, and ensures consistency across your application! 🚀

🤝 Next Steps

Congratulations on becoming a Factory Pattern expert! 🏆 Here’s what to explore next:

  1. Builder Pattern 🏗️ - When you need even more control over object creation
  2. Prototype Pattern 🧬 - Creating objects by cloning existing ones
  3. Dependency Injection 💉 - Advanced factory patterns with IoC containers
  4. Factory + Strategy 🎯 - Combining patterns for ultimate flexibility

Keep practicing with the Factory Pattern in your projects. Try creating factories for:

  • API request handlers 🌐
  • Form validators ✅
  • Data transformers 🔄
  • UI component generators 🎨

Remember, great software is built one pattern at a time! You’re doing amazing! 🌟