Back to Blog
design-patternscsharptypescriptcreational-patterns

Abstract Factory Pattern — FixIt Pro Series #02

FixIt Pro needs to send notifications, but Homeowners and Business clients expect completely different experiences. Learn how the Abstract Factory pattern builds consistent families of objects without coupling to concrete classes.

Series: Design Patterns with FixIt Pro  ·  Episode 02 / 22  ·  Creational Pattern
Previous: #01 — Factory Method Pattern


The Scenario

FixIt Pro has two types of clients:

  • Homeowners — individuals who need quick, friendly communication. They get SMS alerts, simple email receipts, and a clean mobile-first dashboard.
  • Business clients — property managers and estate agents handling multiple units. They need formal notifications, detailed invoice emails, and a data-rich admin portal.

When a job card is issued, the system needs to notify the right people with the right UI and messaging style — depending on which client type raised the request.

The naive approach? Sprinkle if (clientType == "homeowner") checks throughout the notification, email, and UI code. That path ends in a maintenance nightmare. The Abstract Factory pattern solves this cleanly.


What Is the Abstract Factory Pattern?

Provide an interface for creating families of related objects without specifying their concrete classes.

Where the Factory Method creates one product, the Abstract Factory creates a suite of related products that are designed to work together.

In FixIt Pro terms, each client type needs a notification kit — a matched set of:

Product Homeowner version Business version
IAlertNotifier Friendly SMS-style alert Formal system notification
IEmailReceipt Simple job confirmation email Detailed invoice-style email
IDashboardWidget Mobile-friendly status card Data-rich admin summary

The factory guarantees that when you ask for a Homeowner kit, all three products are Homeowner-flavoured. You'll never accidentally mix a business invoice with a homeowner SMS alert.

The four participants

Role FixIt Pro equivalent
Abstract Factory IClientNotificationFactory
Concrete Factories HomeownerNotificationFactory, BusinessNotificationFactory
Abstract Products IAlertNotifier, IEmailReceipt, IDashboardWidget
Concrete Products SmsAlert, FormalAlert, SimpleReceipt, InvoiceReceipt, etc.

C# Implementation

// ── Abstract Products ──────────────────────────────────────

public interface IAlertNotifier
{
    void SendAlert(string jobId, string message);
}

public interface IEmailReceipt
{
    void SendReceipt(string jobId, string clientEmail);
}

public interface IDashboardWidget
{
    void Render(string jobId, string status);
}

// ── Concrete Products — Homeowner ──────────────────────────

public class SmsStyleAlert : IAlertNotifier
{
    public void SendAlert(string jobId, string message) =>
        Console.WriteLine($"📱 SMS to homeowner | Job #{jobId}: {message}");
}

public class SimpleEmailReceipt : IEmailReceipt
{
    public void SendReceipt(string jobId, string clientEmail) =>
        Console.WriteLine($"📧 Simple receipt → {clientEmail} | Job #{jobId} confirmed.");
}

public class MobileStatusCard : IDashboardWidget
{
    public void Render(string jobId, string status) =>
        Console.WriteLine($"📲 Mobile card | Job #{jobId} — Status: {status}");
}

// ── Concrete Products — Business ───────────────────────────

public class FormalSystemAlert : IAlertNotifier
{
    public void SendAlert(string jobId, string message) =>
        Console.WriteLine($"🔔 System notification | Job #{jobId}: {message} [ref: BSNS]");
}

public class InvoiceEmailReceipt : IEmailReceipt
{
    public void SendReceipt(string jobId, string clientEmail) =>
        Console.WriteLine($"🧾 Invoice receipt → {clientEmail} | Job #{jobId} — Tax invoice attached.");
}

public class AdminSummaryWidget : IDashboardWidget
{
    public void Render(string jobId, string status) =>
        Console.WriteLine($"🖥️  Admin summary | Job #{jobId} — Status: {status} | SLA tracked");
}

// ── Abstract Factory ───────────────────────────────────────

public interface IClientNotificationFactory
{
    IAlertNotifier   CreateAlertNotifier();
    IEmailReceipt    CreateEmailReceipt();
    IDashboardWidget CreateDashboardWidget();
}

// ── Concrete Factories ─────────────────────────────────────

public class HomeownerNotificationFactory : IClientNotificationFactory
{
    public IAlertNotifier   CreateAlertNotifier()   => new SmsStyleAlert();
    public IEmailReceipt    CreateEmailReceipt()    => new SimpleEmailReceipt();
    public IDashboardWidget CreateDashboardWidget() => new MobileStatusCard();
}

public class BusinessNotificationFactory : IClientNotificationFactory
{
    public IAlertNotifier   CreateAlertNotifier()   => new FormalSystemAlert();
    public IEmailReceipt    CreateEmailReceipt()    => new InvoiceEmailReceipt();
    public IDashboardWidget CreateDashboardWidget() => new AdminSummaryWidget();
}

// ── Client Code — Job Dispatcher ──────────────────────────

public class JobDispatcher
{
    private readonly IAlertNotifier   _alertNotifier;
    private readonly IEmailReceipt    _emailReceipt;
    private readonly IDashboardWidget _dashboardWidget;

    public JobDispatcher(IClientNotificationFactory factory)
    {
        _alertNotifier   = factory.CreateAlertNotifier();
        _emailReceipt    = factory.CreateEmailReceipt();
        _dashboardWidget = factory.CreateDashboardWidget();
    }

    public void DispatchJob(string jobId, string clientEmail)
    {
        Console.WriteLine($"\n--- Dispatching Job #{jobId} ---");
        _alertNotifier.SendAlert(jobId,   "Your job has been assigned to a handyman.");
        _emailReceipt.SendReceipt(jobId,  clientEmail);
        _dashboardWidget.Render(jobId,    "Assigned");
    }
}

// ── Program ────────────────────────────────────────────────

class Program
{
    static void Main()
    {
        // Homeowner flow
        var homeownerDispatcher = new JobDispatcher(new HomeownerNotificationFactory());
        homeownerDispatcher.DispatchJob("JC-001", "[email protected]");

        // Business flow
        var businessDispatcher = new JobDispatcher(new BusinessNotificationFactory());
        businessDispatcher.DispatchJob("JC-002", "[email protected]");
    }
}

Output:

--- Dispatching Job #JC-001 ---
📱 SMS to homeowner | Job #JC-001: Your job has been assigned to a handyman.
📧 Simple receipt → [email protected] | Job #JC-001 confirmed.
📲 Mobile card | Job #JC-001 — Status: Assigned

--- Dispatching Job #JC-002 ---
🔔 System notification | Job #JC-002: Your job has been assigned to a handyman. [ref: BSNS]
🧾 Invoice receipt → [email protected] | Job #JC-002 — Tax invoice attached.
🖥️  Admin summary | Job #JC-002 — Status: Assigned | SLA tracked

TypeScript Implementation

// ── Abstract Products ──────────────────────────────────────

interface IAlertNotifier {
  sendAlert(jobId: string, message: string): void;
}

interface IEmailReceipt {
  sendReceipt(jobId: string, clientEmail: string): void;
}

interface IDashboardWidget {
  render(jobId: string, status: string): void;
}

// ── Concrete Products — Homeowner ──────────────────────────

class SmsStyleAlert implements IAlertNotifier {
  sendAlert(jobId: string, message: string): void {
    console.log(`📱 SMS to homeowner | Job #${jobId}: ${message}`);
  }
}

class SimpleEmailReceipt implements IEmailReceipt {
  sendReceipt(jobId: string, clientEmail: string): void {
    console.log(`📧 Simple receipt → ${clientEmail} | Job #${jobId} confirmed.`);
  }
}

class MobileStatusCard implements IDashboardWidget {
  render(jobId: string, status: string): void {
    console.log(`📲 Mobile card | Job #${jobId} — Status: ${status}`);
  }
}

// ── Concrete Products — Business ───────────────────────────

class FormalSystemAlert implements IAlertNotifier {
  sendAlert(jobId: string, message: string): void {
    console.log(`🔔 System notification | Job #${jobId}: ${message} [ref: BSNS]`);
  }
}

class InvoiceEmailReceipt implements IEmailReceipt {
  sendReceipt(jobId: string, clientEmail: string): void {
    console.log(`🧾 Invoice receipt → ${clientEmail} | Job #${jobId} — Tax invoice attached.`);
  }
}

class AdminSummaryWidget implements IDashboardWidget {
  render(jobId: string, status: string): void {
    console.log(`🖥️  Admin summary | Job #${jobId} — Status: ${status} | SLA tracked`);
  }
}

// ── Abstract Factory ───────────────────────────────────────

interface IClientNotificationFactory {
  createAlertNotifier():   IAlertNotifier;
  createEmailReceipt():    IEmailReceipt;
  createDashboardWidget(): IDashboardWidget;
}

// ── Concrete Factories ─────────────────────────────────────

class HomeownerNotificationFactory implements IClientNotificationFactory {
  createAlertNotifier():   IAlertNotifier   { return new SmsStyleAlert(); }
  createEmailReceipt():    IEmailReceipt    { return new SimpleEmailReceipt(); }
  createDashboardWidget(): IDashboardWidget { return new MobileStatusCard(); }
}

class BusinessNotificationFactory implements IClientNotificationFactory {
  createAlertNotifier():   IAlertNotifier   { return new FormalSystemAlert(); }
  createEmailReceipt():    IEmailReceipt    { return new InvoiceEmailReceipt(); }
  createDashboardWidget(): IDashboardWidget { return new AdminSummaryWidget(); }
}

// ── Client Code — Job Dispatcher ──────────────────────────

class JobDispatcher {
  private alertNotifier:   IAlertNotifier;
  private emailReceipt:    IEmailReceipt;
  private dashboardWidget: IDashboardWidget;

  constructor(factory: IClientNotificationFactory) {
    this.alertNotifier   = factory.createAlertNotifier();
    this.emailReceipt    = factory.createEmailReceipt();
    this.dashboardWidget = factory.createDashboardWidget();
  }

  dispatchJob(jobId: string, clientEmail: string): void {
    console.log(`\n--- Dispatching Job #${jobId} ---`);
    this.alertNotifier.sendAlert(jobId,   "Your job has been assigned to a handyman.");
    this.emailReceipt.sendReceipt(jobId,  clientEmail);
    this.dashboardWidget.render(jobId,    "Assigned");
  }
}

// ── Main ───────────────────────────────────────────────────

const homeownerDispatcher = new JobDispatcher(new HomeownerNotificationFactory());
homeownerDispatcher.dispatchJob("JC-001", "[email protected]");

const businessDispatcher = new JobDispatcher(new BusinessNotificationFactory());
businessDispatcher.dispatchJob("JC-002", "[email protected]");

C# vs TypeScript — Key Differences

Aspect C# TypeScript
Interface keyword public interface interface (no access modifier)
Implements : IAlertNotifier implements IAlertNotifier
Constructor injection public JobDispatcher(IClientNotificationFactory factory) constructor(factory: IClientNotificationFactory)
Private fields private readonly IAlertNotifier _alertNotifier private alertNotifier: IAlertNotifier
Naming convention PascalCase methods camelCase methods

Once again, the structure is identical. The Abstract Factory pattern translates directly — only syntax differs.


Factory Method vs Abstract Factory

A common point of confusion:

Factory Method Abstract Factory
Creates One product A family of related products
Mechanism Inheritance (subclass overrides a method) Composition (object holds a factory reference)
Use when You need to vary one object type You need a consistent set of objects to work together
FixIt Pro Creating a single job card type Creating a full notification kit per client type

The Abstract Factory often uses Factory Methods internally. They complement each other.


When to Use the Abstract Factory

Use it when:

  • Your system needs to work with multiple families of related objects
  • You want to enforce consistency — objects from one family should never be mixed with another
  • You need to swap out entire product families at runtime (e.g. switching from Homeowner to Business mode)

Avoid it when:

  • You only have one product variant — a Factory Method or simple constructor is sufficient
  • Adding new product types (not just families) is common — this requires changing the abstract factory interface itself, which can become unwieldy

Real-World Takeaway

The Abstract Factory is the backbone of theming systems, multi-tenant SaaS platforms, and cross-platform UI toolkits. In .NET, DbProviderFactory is a classic example — it returns a consistent family of database objects (connection, command, reader) regardless of whether you're targeting SQL Server, PostgreSQL, or SQLite.

In FixIt Pro, this pattern means the JobDispatcher never needs to know whether it's dealing with a homeowner or a business client. It just calls the factory, gets a consistent kit, and does its job. Adding a third client type — say, a Government contract client — means writing one new factory class. The dispatcher never changes.


Source Code

github.com/antonlungameni/fixit-pro-design-patterns

fixit-pro-design-patterns/
├── csharp/Creational/02-AbstractFactory/
└── typescript/Creational/02-AbstractFactory/

Previous: #01 — Factory Method Pattern
Next up: #03 — Builder Pattern
Constructing a complex JobCard step by step — urgency levels, parts lists, photos, scheduling — without a constructor that takes 12 arguments.