21 Sept 2025
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.
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.
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
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:
Let’s say we are currently support three booking themes

Depending on the theme (Standard, Holiday, Corporate), the booking flow must work together as a family.
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.
BookingService is struggling with too many responsibilities like what resource to book, what template to render and what invoice to generate. It should have the only responsibility of booking a resource for a specific timeslot without worrying about the themeHow 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?
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:

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.

AbstractFactory interface that declares set of related factory methods.
createProductA(): ProductAcreateProductB(): ProductBcreateProductC(): ProductCAbstractFactory
ConcreteFactory1ConcreteFactory2AbstractFactory interface.
This diagram highlights:
Booking, Invoice, Template) at the left.BookingFactory) defining creation methods.HolidayFactory, StandardFactory) that implement the abstract factory and ensure consistency across product families.HolidayFactory returns holiday-themed Booking, Invoice, and Template , StandardBookingFactory returns standard-themed ones and CorporateFactory returns corporate-themed Booking, Invoice, and TemplateBookingService, which depends only on BookingFactory, and not on concrete classes.// 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.
There's nothing stronger than a family - Vin Diesel :)
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