Mail

Abstract Factory Pattern - Creating Families of Related Objects

21 Sept 2025

Table of contents

Prerequisites

These are the basic prerequisites for this blog. If you’re not familiar with first two points. This blog might not be suitable for you. Otherwise, you’re good to go! The examples are really simple and can be easily replicated in other languages.

Introduction

Hey devs, welcome back to the design pattern series! 👋

Last time, we looked at the Factory Method pattern. Today, we’re leveling up to its big sibling the Abstract Factory pattern. As always, we’re sticking with our Booking System example so everything feels connected.

What is Abstract Factory Pattern

Abstract Factory

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

The Problem

In our Booking system the customers can reserve a hotel room, theatre seat, or even a vehicle for a specific timeslot based on the availability status. For a simple booking flow we need three related things:

  1. Booking object (Resource + Time + Customer)
  2. Invoice (Payment information, Discounts, Taxes and etc.,)
  3. Template (Confirmation email / PDF design)

Let’s say we are currently support three booking themes

CurrentProblem.png

Depending on the theme (Standard, Holiday, Corporate), the booking flow must work together as a family.

Initial Implementation

This is what many of us would write at first glance:

type Theme = "standard" | "holiday" | "corporate";

class BookingService {
  createBooking(theme: Theme, resource: Resource, customer: Customer, timeSlot: Timestamp) {
    // Booking
    let booking;
    if (resource.type === "room") {
      booking = new RoomBooking(customer, timeSlot);
    } else if (resource.type === "seat") {
      booking = new SeatBooking(customer, timeSlot);
    } else {
      booking = new VehicleBooking(customer, timeSlot);
    }

    // Invoice
    let invoice;
    if (theme === "holiday") {
      invoice = new HolidayInvoice(booking);
    } else if (theme === "corporate"){
	    invoice = new CorporateInvoice(booking);
    } else {
      invoice = new StandardInvoice(booking);
    }

    // Template
    let template;
    if (theme === "holiday") {
      template = new HolidayTemplate();
    } else {
      template = new StandardTemplate();
    }

    // Processing
    booking.reserve();
    invoice.generate();
    template.render();
  }
  // other CRUD methods
}

It works without any errors believe me…. but you can already feel the cracks.

How do we make sure that when someone books under the Holiday theme, they also get the correct Holiday invoice and Holiday template without these if (theme === "holiday") checks everywhere?

Solution

The Abstract Factory pattern says:

Group related objects into families, and provide an interface for creating them without specifying their exact classes.

In our Booking System, a family is:

Solution Diagram

The key idea here is each family stays consistent. For example, a HolidayFactory always gives you a holiday booking, holiday invoice, and holiday template. A StandardFactory produces the standard versions of those same products.

Abstract Factory Diagram

AbstractFactoryPattern.png

Solution steps

  1. Define abstract products
    • Interfaces for the family of products.
    • Each defines the contract (methods/properties) but not the implementation.
  2. Define the abstract factory interface
    • A AbstractFactory interface that declares set of related factory methods.
      • createProductA(): ProductA
      • createProductB(): ProductB
      • createProductC(): ProductC
  3. Create concrete factories by implementing the AbstractFactory
    • ConcreteFactory1
    • ConcreteFactory2
  4. Client code
    • Works only with the AbstractFactory interface.
    • Doesn’t care which concrete factory is being used. It just knows it will get a consistent family of products.
    • Follows the Single Responsibility Principle and Open/Closed Principle

Solution Diagram

BookingFactoryPattern.png

This diagram highlights:

Basic Implementation

// abstract products
interface Booking {
  reserve(): void;
}

interface Invoice {
  generate(): void;
}

interface Template {
  render(): void;
}

// concrete products
class RoomBooking implements Booking {
  constructor(private customer: Customer, private timeSlot: Timestamp) {}
  reserve() {
    // room booking logic
  }
}
class SeatBooking implements Booking {
  constructor(private customer: Customer, private timeSlot: Timestamp) {}
  reserve() {
    // seat booking logic
  }
}
class VehicleBooking implements Booking {
  constructor(private customer: Customer, private timeSlot: Timestamp) {}
  reserve() {
    // vehicle booking logic
  }
}

class StandardInvoice implements Invoice {
  constructor(private booking: Booking) {}
  generate() {
    // standard invoice logic
  }
}

class HolidayInvoice implements Invoice {
  constructor(private booking: Booking) {}
  generate() {
    // holiday invoice logic
  }
}

class StandardTemplate implements Template {
  render() {
    // standard template logic
  }
}

class HolidayTemplate implements Template {
  render() {
    // holiday template logic
  }
}
// abstract factory
interface BookingFactory {
  createBooking(resource: Resource, customer: Customer, timeSlot: Timestamp): Booking;
  createInvoice(booking: Booking): Invoice;
  createTemplate(): Template;
}
// concreate factories
class StandardFactory implements BookingFactory {
  createBooking(resource: Resource, customer: Customer, timeSlot: Timestamp): Booking {
    switch (resource.type) {
      case "room": return new RoomBooking(customer, timeSlot);
      case "seat": return new SeatBooking(customer, timeSlot);
      case "vehicle": return new VehicleBooking(customer, timeSlot);
    }
  }
  createInvoice(booking: Booking): Invoice {
    return new StandardInvoice(booking);
  }
  createTemplate(): Template {
    return new StandardTemplate();
  }
}

class HolidayFactory implements BookingFactory {
  createBooking(resource: Resource, customer: Customer, timeSlot: Timestamp): Booking {
    switch (resource.type) {
      case "room": return new HolidayRoomBooking(customer, timeSlot);
      case "seat": return new HolidaySeatBooking(customer, timeSlot);
      case "vehicle": return new HolidayVehicleBooking(customer, timeSlot);
    }
  }
  createInvoice(booking: Booking): Invoice {
    return new HolidayInvoice(booking);
  }
  createTemplate(): Template {
    return new HolidayTemplate();
  }
}
...

BookingService.ts

// Client code
class BookingService {

  constructor(private factory: BookingFactory) {}

  book(resource: Resource, customer: Customer, timeSlot: TimeStamp) {
    const booking = this.factory.createBooking(resource, customer, timeSlot);
    const invoice = this.factory.createInvoice(booking);
    const template = this.factory.createTemplate();

    booking.reserve();
    invoice.generate();
    template.render();
  }
  // other methods
}

index.ts

import { BookingService } from "./BookingService";
import { StandardFactory } from "./factories/StandardBookingFactory";
import { HolidayFactory } from "./factories/HolidayBookingFactory";

// Utility: check if given timestamp falls on weekend
function isWeekend(timestamp: number): boolean {
  const date = new Date(timestamp);
  const day = date.getDay();
  return day === 0 || day === 6;
}

// Factory selector
function getBookingFactory(timestamp: number): BookingFactory {
  if (isWeekend(timestamp)) {
    return new HolidayBookingFactory();
  } else {
    return new StandardBookingFactory();
  }
}

// Example usage
function main() {
  const date = new Date().getTime();

  const bookingService = new BookingService(getBookingFactory(date));
  bookingService.book(resource, customer, date);
}

main();

Now the BookingService doesn’t care about concrete classes at all. It just uses the abstract factory. The code using BookingService simply books based on the date. It doesn’t need to worry about how the booking is created, which type of booking it is, or the underlying logic.

Instead, the focus remains only on when to book and who/what to book, while the factories handle the rest.

Pros and Cons

Pros

Cons

When not to use it

There's nothing stronger than a family - Vin Diesel :)

Conclusion

Woo huh! we did it. That’s it for today. I tried my best to explain this pattern. In the next post, we’ll explore another pattern in our Design Pattern series. if you have any queries are suggestions please feel free to reach me out.

Code, learn, refactor, repeat

Reference

Stay Connected

GitHub LinkedIn X Daily.dev Email

© 2025 Chiristo. Feel free to share or reference this post with proper credit