Abstract Factory Pattern: Mastering Object Creation in TypeScript

Explore the Abstract Factory Pattern in TypeScript, a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

4.3 Abstract Factory Pattern

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 in scenarios where a system must be independent of how its objects are created, composed, and represented. Let’s delve into the intricacies of this pattern and explore how it can be effectively implemented in TypeScript.

Understanding the Abstract Factory Pattern

The Abstract Factory Pattern is designed to solve the problem of creating related objects that must be used together, ensuring that the system remains flexible and scalable. By abstracting the creation process, the pattern allows for the easy interchangeability of product families, promoting consistency and adherence to the Open/Closed Principle, which states that software entities should be open for extension but closed for modification.

Problem Statement

Consider a scenario where you are developing a user interface library that supports multiple themes, such as light and dark modes. Each theme requires a consistent set of UI components, like buttons, text fields, and checkboxes. The challenge is to create these components in a way that allows for easy switching between themes without altering the client code.

Key Participants in the Abstract Factory Pattern

The Abstract Factory Pattern involves several key participants, each playing a crucial role in the pattern’s structure:

  • AbstractFactory: An interface with methods for creating abstract products. It declares a set of methods for creating each of the abstract products.

  • ConcreteFactory: Implements the creation methods for specific product families. Each factory corresponds to a specific variant of products.

  • AbstractProduct: Interfaces or abstract classes for products. These define the operations that all concrete products must implement.

  • ConcreteProduct: Specific implementations of products. Each product variant is implemented as a separate class.

  • Client: Uses only the interfaces declared by AbstractFactory and AbstractProduct. It is decoupled from the concrete classes and relies on the factory to create the products.

Diagrammatic Representation

To better understand the relationships between these participants, let’s visualize the Abstract Factory Pattern using a class diagram:

    classDiagram
	    class AbstractFactory {
	        <<interface>>
	        +createButton() AbstractButton
	        +createCheckbox() AbstractCheckbox
	    }
	
	    class ConcreteFactory1 {
	        +createButton() ConcreteButton1
	        +createCheckbox() ConcreteCheckbox1
	    }
	
	    class ConcreteFactory2 {
	        +createButton() ConcreteButton2
	        +createCheckbox() ConcreteCheckbox2
	    }
	
	    class AbstractButton {
	        <<interface>>
	        +render()
	    }
	
	    class ConcreteButton1 {
	        +render()
	    }
	
	    class ConcreteButton2 {
	        +render()
	    }
	
	    class AbstractCheckbox {
	        <<interface>>
	        +render()
	    }
	
	    class ConcreteCheckbox1 {
	        +render()
	    }
	
	    class ConcreteCheckbox2 {
	        +render()
	    }
	
	    AbstractFactory <|.. ConcreteFactory1
	    AbstractFactory <|.. ConcreteFactory2
	    AbstractButton <|.. ConcreteButton1
	    AbstractButton <|.. ConcreteButton2
	    AbstractCheckbox <|.. ConcreteCheckbox1
	    AbstractCheckbox <|.. ConcreteCheckbox2
	    ConcreteFactory1 --> ConcreteButton1
	    ConcreteFactory1 --> ConcreteCheckbox1
	    ConcreteFactory2 --> ConcreteButton2
	    ConcreteFactory2 --> ConcreteCheckbox2

Implementing the Abstract Factory Pattern in TypeScript

Let’s implement the Abstract Factory Pattern in TypeScript by creating a UI component library that supports different themes.

Step 1: Define Abstract Products

First, we define the abstract products, which are the interfaces for the UI components.

1// AbstractProductA
2interface Button {
3    render(): void;
4}
5
6// AbstractProductB
7interface Checkbox {
8    render(): void;
9}

Step 2: Create Concrete Products

Next, we implement the concrete products for each theme.

 1// ConcreteProductA1
 2class LightButton implements Button {
 3    render(): void {
 4        console.log("Rendering a light-themed button.");
 5    }
 6}
 7
 8// ConcreteProductA2
 9class DarkButton implements Button {
10    render(): void {
11        console.log("Rendering a dark-themed button.");
12    }
13}
14
15// ConcreteProductB1
16class LightCheckbox implements Checkbox {
17    render(): void {
18        console.log("Rendering a light-themed checkbox.");
19    }
20}
21
22// ConcreteProductB2
23class DarkCheckbox implements Checkbox {
24    render(): void {
25        console.log("Rendering a dark-themed checkbox.");
26    }
27}

Step 3: Define the Abstract Factory

We then define the abstract factory interface, which declares methods for creating each of the abstract products.

1interface UIComponentFactory {
2    createButton(): Button;
3    createCheckbox(): Checkbox;
4}

Step 4: Implement Concrete Factories

Now, we implement the concrete factories for each theme.

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

Step 5: Implement the Client

Finally, we implement the client, which uses the abstract factory to create the products.

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

Promoting Consistency and Supporting the Open/Closed Principle

The Abstract Factory Pattern promotes consistency among products by ensuring that related objects are created together. This is particularly important in scenarios where the products must work seamlessly with each other, such as in UI libraries or document processing systems.

Moreover, the pattern supports the Open/Closed Principle by allowing new product families to be added without modifying existing code. This is achieved by introducing new concrete factories and products, which can be seamlessly integrated into the system.

Try It Yourself

To deepen your understanding of the Abstract Factory Pattern, try modifying the code examples to add a new theme, such as a “High Contrast” theme. Implement the necessary concrete products and factory, and update the client code to use the new theme.

Visualizing the Abstract Factory Pattern in Action

Let’s visualize the process of creating UI components using the Abstract Factory Pattern with a sequence diagram:

    sequenceDiagram
	    participant Client
	    participant AbstractFactory
	    participant ConcreteFactory1
	    participant ConcreteProductA1
	    participant ConcreteProductB1
	
	    Client->>AbstractFactory: createButton()
	    AbstractFactory->>ConcreteFactory1: createButton()
	    ConcreteFactory1->>ConcreteProductA1: new LightButton()
	    ConcreteProductA1-->>ConcreteFactory1: LightButton instance
	    ConcreteFactory1-->>AbstractFactory: LightButton instance
	    AbstractFactory-->>Client: LightButton instance
	
	    Client->>AbstractFactory: createCheckbox()
	    AbstractFactory->>ConcreteFactory1: createCheckbox()
	    ConcreteFactory1->>ConcreteProductB1: new LightCheckbox()
	    ConcreteProductB1-->>ConcreteFactory1: LightCheckbox instance
	    ConcreteFactory1-->>AbstractFactory: LightCheckbox instance
	    AbstractFactory-->>Client: LightCheckbox instance

Knowledge Check

  • Question: What is the primary benefit of using the Abstract Factory Pattern?

    • Answer: It provides an interface for creating families of related objects without specifying their concrete classes, promoting consistency and flexibility.
  • Question: How does the Abstract Factory Pattern support the Open/Closed Principle?

    • Answer: By allowing new product families to be added without modifying existing code, adhering to the principle of being open for extension but closed for modification.

Embrace the Journey

Remember, mastering design patterns like the Abstract Factory Pattern is a journey. As you continue to explore and experiment with these patterns, you’ll gain deeper insights into creating flexible, scalable, and maintainable software systems. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…

In this section

Revised on Thursday, April 23, 2026