Implementing Abstract Factory in TypeScript: A Comprehensive Guide

Explore how to implement the Abstract Factory Pattern in TypeScript using interfaces and classes to create scalable and maintainable code.

4.3.1 Implementing Abstract Factory in TypeScript

The 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. This pattern is particularly useful when a system needs to be independent of how its objects are created, composed, and represented. In this section, we’ll explore how to implement the Abstract Factory Pattern in TypeScript, leveraging its powerful features like interfaces and classes.

Understanding the Abstract Factory Pattern

Before diving into the implementation, let’s briefly understand the core concepts of the Abstract Factory Pattern:

  • Abstract Products: Define interfaces or abstract classes for each type of product that can be created.
  • Concrete Products: Implement the abstract product interfaces or extend the abstract classes to create specific product variants.
  • Abstract Factory: An interface with methods for creating each type of product.
  • Concrete Factories: Implement the Abstract Factory interface to produce a family of related products.
  • Client: Uses the factories to create objects but remains independent of the concrete classes.

Step-by-Step Implementation in TypeScript

Let’s walk through the implementation of the Abstract Factory Pattern in TypeScript with a practical example. We’ll create a simple GUI toolkit that supports different themes (e.g., Light and Dark). Each theme will have its own set of UI components like buttons and checkboxes.

Step 1: Define Abstract Product Interfaces

First, define interfaces for the abstract products. These interfaces will declare the methods that all product variants must implement.

 1// Abstract product interface for Buttons
 2interface Button {
 3    render(): void;
 4    onClick(callback: () => void): void;
 5}
 6
 7// Abstract product interface for Checkboxes
 8interface Checkbox {
 9    render(): void;
10    check(): void;
11    uncheck(): void;
12}

Step 2: Create Concrete Product Classes

Next, implement concrete product classes for each theme. These classes will implement the abstract product interfaces.

 1// Concrete product class for Light Button
 2class LightButton implements Button {
 3    render(): void {
 4        console.log("Rendering a light-themed button.");
 5    }
 6
 7    onClick(callback: () => void): void {
 8        console.log("Light button clicked.");
 9        callback();
10    }
11}
12
13// Concrete product class for Dark Button
14class DarkButton implements Button {
15    render(): void {
16        console.log("Rendering a dark-themed button.");
17    }
18
19    onClick(callback: () => void): void {
20        console.log("Dark button clicked.");
21        callback();
22    }
23}
24
25// Concrete product class for Light Checkbox
26class LightCheckbox implements Checkbox {
27    render(): void {
28        console.log("Rendering a light-themed checkbox.");
29    }
30
31    check(): void {
32        console.log("Light checkbox checked.");
33    }
34
35    uncheck(): void {
36        console.log("Light checkbox unchecked.");
37    }
38}
39
40// Concrete product class for Dark Checkbox
41class DarkCheckbox implements Checkbox {
42    render(): void {
43        console.log("Rendering a dark-themed checkbox.");
44    }
45
46    check(): void {
47        console.log("Dark checkbox checked.");
48    }
49
50    uncheck(): void {
51        console.log("Dark checkbox unchecked.");
52    }
53}

Step 3: Define the Abstract Factory Interface

Create an interface for the Abstract Factory. This interface will declare methods for creating each type of product.

1// Abstract factory interface
2interface GUIFactory {
3    createButton(): Button;
4    createCheckbox(): Checkbox;
5}

Step 4: Implement Concrete Factory Classes

Implement concrete factory classes for each theme. These classes will produce the appropriate product variants.

 1// Concrete factory class for Light theme
 2class LightThemeFactory implements GUIFactory {
 3    createButton(): Button {
 4        return new LightButton();
 5    }
 6
 7    createCheckbox(): Checkbox {
 8        return new LightCheckbox();
 9    }
10}
11
12// Concrete factory class for Dark theme
13class DarkThemeFactory implements GUIFactory {
14    createButton(): Button {
15        return new DarkButton();
16    }
17
18    createCheckbox(): Checkbox {
19        return new DarkCheckbox();
20    }
21}

Step 5: Use Dependency Injection to Provide the Appropriate Factory

To keep the client code independent of the concrete classes, use dependency injection to provide the appropriate factory.

 1// Client code
 2class Application {
 3    private button: Button;
 4    private checkbox: Checkbox;
 5
 6    constructor(factory: GUIFactory) {
 7        this.button = factory.createButton();
 8        this.checkbox = factory.createCheckbox();
 9    }
10
11    render(): void {
12        this.button.render();
13        this.checkbox.render();
14    }
15}
16
17// Usage
18const lightFactory: GUIFactory = new LightThemeFactory();
19const darkFactory: GUIFactory = new DarkThemeFactory();
20
21const lightApp = new Application(lightFactory);
22lightApp.render();
23
24const darkApp = new Application(darkFactory);
25darkApp.render();

Leveraging TypeScript Features

TypeScript’s features such as interfaces, classes, and access modifiers play a crucial role in implementing the Abstract Factory Pattern:

  • Interfaces: Define contracts for the products and factories, ensuring consistency across different implementations.
  • Classes: Implement concrete products and factories, encapsulating the logic specific to each variant.
  • Access Modifiers: Control the visibility of class members, enforcing encapsulation and hiding implementation details.

Keeping Client Code Independent

One of the primary goals of the Abstract Factory Pattern is to keep client code independent of concrete classes. By relying on interfaces and abstract classes, we ensure that the client interacts only with the abstract layer, making it easy to switch between different product families without modifying the client code.

Visualizing the Abstract Factory Pattern

To better understand the relationships between the components of the Abstract Factory Pattern, let’s visualize it using a class diagram.

    classDiagram
	    class Button {
	        +render()
	        +onClick(callback: () => void)
	    }
	    class Checkbox {
	        +render()
	        +check()
	        +uncheck()
	    }
	    class LightButton {
	        +render()
	        +onClick(callback: () => void)
	    }
	    class DarkButton {
	        +render()
	        +onClick(callback: () => void)
	    }
	    class LightCheckbox {
	        +render()
	        +check()
	        +uncheck()
	    }
	    class DarkCheckbox {
	        +render()
	        +check()
	        +uncheck()
	    }
	    class GUIFactory {
	        +createButton() Button
	        +createCheckbox() Checkbox
	    }
	    class LightThemeFactory {
	        +createButton() Button
	        +createCheckbox() Checkbox
	    }
	    class DarkThemeFactory {
	        +createButton() Button
	        +createCheckbox() Checkbox
	    }
	    Button <|-- LightButton
	    Button <|-- DarkButton
	    Checkbox <|-- LightCheckbox
	    Checkbox <|-- DarkCheckbox
	    GUIFactory <|-- LightThemeFactory
	    GUIFactory <|-- DarkThemeFactory

Try It Yourself

Experiment with the code examples by adding new product types or themes. For instance, create a “HighContrast” theme with its own set of buttons and checkboxes. This exercise will help reinforce your understanding of the Abstract Factory Pattern and its flexibility in accommodating new product families.

Key Takeaways

  • Abstract Factory Pattern: Provides an interface for creating families of related objects without specifying their concrete classes.
  • TypeScript Features: Leverage interfaces, classes, and access modifiers to implement the pattern.
  • Dependency Injection: Use dependency injection to provide the appropriate factory, keeping client code independent of concrete classes.
  • Flexibility: Easily extend the system with new product families by adding new concrete factories and products.

Further Reading

For more information on the Abstract Factory Pattern and its applications, consider exploring the following resources:

Embrace the Journey

Remember, mastering design patterns is a journey. As you continue to explore and implement different patterns, you’ll gain a deeper understanding of how to create scalable, maintainable, and flexible software systems. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026