TypeScript Abstract Factory Pattern Example

1. Definition

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

2. Problem Statement

Imagine a scenario where you need to ensure the compatibility of products, and you want to produce multiple variants of these products (e.g., different UI elements for different operating systems). Directly creating instances of these classes can lead to a mix of incompatible products.

3. Solution

The Abstract Factory pattern allows you to produce families of related objects without specifying their concrete classes. Each factory produces products that conform to one product variant.

4. Real-World Use Cases

1. GUI libraries that must provide controls for multiple operating systems.

2. E-commerce websites that need to generate different views for mobile and desktop platforms.

3. Cross-platform toolkits producing UI elements that adhere to multiple platform standards.

5. Implementation Steps

1. Define abstract product interfaces for each type of product.

2. Create concrete product classes for each product variant.

3. Create an abstract factory interface declaring the creation methods for each type of abstract product.

4. Implement a set of concrete factories corresponding to each product variant.

6. Implementation in TypeScript

// Step 1: Define abstract product interfaces
interface Button {
    click(): string;
}
interface Checkbox {
    check(): string;
}
// Step 2: Create concrete products for each product variant
// Windows family
class WindowsButton implements Button {
    click(): string {
        return "Windows Button clicked!";
    }
}
class WindowsCheckbox implements Checkbox {
    check(): string {
        return "Windows Checkbox checked!";
    }
}
// MacOS family
class MacOSButton implements Button {
    click(): string {
        return "MacOS Button clicked!";
    }
}
class MacOSCheckbox implements Checkbox {
    check(): string {
        return "MacOS Checkbox checked!";
    }
}
// Step 3: Create abstract factory interface
interface GUIFactory {
    createButton(): Button;
    createCheckbox(): Checkbox;
}
// Step 4: Implement concrete factories
class WindowsFactory implements GUIFactory {
    createButton(): Button {
        return new WindowsButton();
    }
    createCheckbox(): Checkbox {
        return new WindowsCheckbox();
    }
}
class MacOSFactory implements GUIFactory {
    createButton(): Button {
        return new MacOSButton();
    }
    createCheckbox(): Checkbox {
        return new MacOSCheckbox();
    }
}
// Usage:
const factoryWindows = new WindowsFactory();
const buttonWindows = factoryWindows.createButton();
const checkboxWindows = factoryWindows.createCheckbox();
const factoryMacOS = new MacOSFactory();
const buttonMacOS = factoryMacOS.createButton();
const checkboxMacOS = factoryMacOS.createCheckbox();
console.log(buttonWindows.click());      // Outputs: Windows Button clicked!
console.log(checkboxWindows.check());    // Outputs: Windows Checkbox checked!
console.log(buttonMacOS.click());        // Outputs: MacOS Button clicked!
console.log(checkboxMacOS.check());      // Outputs: MacOS Checkbox checked!

Output:

Windows Button clicked!
Windows Checkbox checked!
MacOS Button clicked!
MacOS Checkbox checked!

Explanation:

The Abstract Factory pattern promotes the creation of object families that maintain consistency among its members, ensuring they remain compatible.

 In the provided TypeScript implementation, GUIFactory serves as an abstract factory that declares the creation methods for Button and Checkbox interfaces. 

We have two concrete factories (WindowsFactory and MacOSFactory) that implement these creation methods to produce the objects of the respective family. This ensures that the products (buttons and checkboxes) are consistent within their family, maintaining compatibility.

7. When to use?

The Abstract Factory pattern is useful when:

1. A system needs to be independent of how its objects are created, composed, and represented.

2. A system is configured with multiple families of products and requires only one family to be instantiated at a time.

3. The family of related product objects is designed to work together, and this constraint needs to be enforced.


Comments