Abstract Factory Design Pattern in TypeScript (Part 1): Intent, Motivation & Applicability

Hooman Momtaheni
5 min readMay 5, 2024

--

In this series of articles, I intend to clarify the specific principles mentioned in the book “Design Patterns: Elements of Reusable Object-Oriented Software”. I tried to explain the difficult areas with examples from the TypeScript language.

Intent

Abstract Factory design pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern formerly unkown as “Kit” design pattern.

Motivation

Consider a user interface toolkit (or UI framework) that supports multiple look-and-feel standards, such as Motif and Presentation Manager*. Different look-and-feels define different appearances and behaviors for user interface “widgets” like scroll bars, windows, and buttons. To be portable** across look-and-feel standards, an application (who instantiates and uses the widgets) should not hard-code its widgets for a particular look and feel. Instantiating look and feel-specific classes of widgets throughout the application makes it hard to change the look and feel later.

*In my article, I stick to using ‘Motif’ and ‘Presentation Manager’ for my example codes. While these UI frameworks are largely outdated, I do this to stay true to the source material. To help you understand, think of them like Bootstrap or Material, but not quite the same.

**Prtability is he ability of the application to be easily adapted or transferred to different environments or standards without needing extensive modifications.

Example of problem:

// Define a simple ScrollBar class
class ScrollBar {
constructor(private orientation: string) {}

render() {
console.log(`Rendering scrollbar with orientation: ${this.orientation}`);
// Render scrollbar implementation
}
}

// Client code that instantiates a ScrollBar
class Application {
private scrollbar: ScrollBar;

constructor() {
// Hard-coded instantiation of ScrollBar for Motif look and feel
this.scrollbar = new ScrollBar("Motif Orientation");
}

runApp() {
this.scrollbar.render();
}
}

// Example usage
const app = new Application();
app.runApp();

Above example demonstrating how the application hard-codes the instantiation of the button specifically for the Motif look and feel. If we later decide to switch to a different look and feel, such as Presentation Manager, we would need to modify the Application class and replace the hard-coded instantiation with the appropriate Material Design button instantiation. This would require changes throughout the codebase, making it hard to maintain and prone to errors and it violates open/closed principle.

We can solve this problem by defining an abstract WidgetFactory class that declares an interface for creating each basic kind of widget. There’s also an abstract class for each kind of widget, and concrete subclasses implement widgets for specific look-and-feel standards. WidgetFactory’s interface has an operation that returns a new widget object for each abstract widget class. Clients call these operations to obtain widget instances, but clients aren’t aware of the concrete classes they’re using. Thus, clients stay independent of the dominant look and feel.

Credit: Design Patterns: Elements of Reusable Object-Oriented Software

Implementation code:

// Abstract widget classes
abstract class Window {
abstract render(): void;
}

abstract class ScrollBar {
abstract render(): void;
}

// Abstract factory class
abstract class WidgetFactory {
abstract createWindow(): Window;
abstract createScrollBar(): ScrollBar;
}

// Concrete implementations for Motif look-and-feel
class MotifWindow extends Window {
render() {
console.log("Rendering Motif window");
// Render Motif window implementation
}
}

class MotifScrollBar extends ScrollBar {
render() {
console.log("Rendering Motif scrollbar");
// Render Motif scrollbar implementation
}
}

class MotifWidgetFactory extends WidgetFactory {
createWindow(): Window {
return new MotifWindow();
}

createScrollBar(): ScrollBar {
return new MotifScrollBar();
}
}

// Concrete implementations for Presentation Manager look-and-feel
class PMWindow extends Window {
render() {
console.log("Rendering PM window");
// Render PM window implementation
}
}

class PMScrollBar extends ScrollBar {
render() {
console.log("Rendering PM scrollbar");
// Render PM scrollbar implementation
}
}

class PMWidgetFactory extends WidgetFactory {
createWindow(): Window {
return new PMWindow();
}

createScrollBar(): ScrollBar {
return new PMScrollBar();
}
}

// Client code
class Application {
private widgetFactory: WidgetFactory;

constructor(widgetFactory: WidgetFactory) {
this.widgetFactory = widgetFactory;
}

createUI() {
const window = this.widgetFactory.createWindow();
const scrollbar = this.widgetFactory.createScrollBar();

window.render();
scrollbar.render();
}
}

// Example usage
const motifFactory = new MotifWidgetFactory();
const PMFactory = new PMWidgetFactory();

const appWithMotif = new Application(motifFactory);
appWithMotif.createUI();

const appWithPM = new Application(PMFactory);
appWithPM.createUI();

There is a concrete subclass of WidgetFactory for each look-and-feel standard. Each subclass implements the operations to create the appropriate widget for the look and feel. For example, the CreateScrollBar operation on the MotifWidgetFactory instantiates and returns a Motif scroll bar, while the corresponding operation on the PMWidgetFactory returns a scroll bar for Presentation Manager. Clients create widgets solely through the WidgetFactory interface and have no knowledge of the classes that implement widgets for a particular look and feel (like MotifScrollBar or PMScrollBar). In other words, clients only have to commit to an interface defined by an abstract class, not a particular concrete class.

A WidgetFactory also enforces dependencies between the concrete widget classes. A Motif scroll bar should be used with a Motif button and a Motif text editor, and that constraint is enforced automatically as a consequence of using a MotifWidgetFactory.

Applicability

Use the Abstract Factory pattern when:

  • A system that uses products should be independent of how those products are created, composed, and represented. (Freedom from knowing the specific implementation details of the objects it uses, allowing it to work with families of related objects in a uniform manner across different contexts or variations.)
  • A system should be configured with one of multiple families of products.
  • A family of related product objects (i.e., Motif, Presentation Manager) is designed to be used together, and you need to enforce this constraint.
  • You want to provide a class library of products (WidgetFactory), and you want to reveal just their interfaces, not their implementations.

** Both the Abstract Factory and Builder design patterns share the characteristic of enabling a system to be independent of how products are created, composed, and represented. However, they serve different purposes and are used in different contexts:

Abstract Factory Pattern:

-The Abstract Factory pattern is used to create families of related or dependent objects without specifying their concrete classes.

-It provides an interface for creating families of related or dependent objects, and each concrete factory is responsible for creating products of a specific variant or theme.

-It is suitable for situations where the client code needs to work with multiple related objects that are part of a cohesive family, such as different themes of GUI components.

Builder Pattern:

-The Builder pattern is used to construct complex objects step by step, allowing the construction process to vary independently from the final representation.

-It separates the construction of a complex object from its representation, allowing the same construction process to create different representations of the object.

  • It is suitable for situations where the construction of an object involves multiple steps or configurations, and the client code needs flexibility in constructing different representations of the same object.

In the next part we’ll discuss structure, participants and conssequences of this highly benefical design pattern.

--

--

Hooman Momtaheni

Full Stack Web Application Developer | Admire foundations and structures | Helping companies have reliable web apps and motivated software development team